Best ios questions in June 2011

What kind of leaks does Objective-C's automatic reference counting (in Xcode 4.2) not prevent/minimize?

13 votes

In the Mac and iOS platforms, memory leaks are often caused by unreleased pointers, so it is (as of now, at least) important to check your allocs, copies and retains to make sure each has a corresponding release message.

Xcode 4.2 introduces automatic reference counting with the latest version of Clang that totally does away with this problem by getting the compiler to memory-manage your stuff for you. That's pretty cool, and it does cut lots of unnecessary, mundane development time and prevent a lot of careless memory leaks that are easy to fix with proper retain/release balance. Even autorelease pools need to be managed differently when you enable ARC for your Mac and iOS apps (as you shouldn't allocate your own NSAutoreleasePools anymore).

But what other memory leaks does it not prevent that I still have to watch out for?

As a bonus, what are the differences between ARC on Mac and iOS and garbage collection on Mac OS X?

The primary memory-related problem you'll still need to be aware of is retain cycles. This occurs when one object has a strong pointer to another, but the target object has a strong pointer back to the original. Even when all other references to these objects are removed, they still will hold on to one another and will not be released. This can also happen indirectly, by a chain of objects that might have the last one in the chain referring back to an earlier object.

It is for this reason that the __unsafe_unretained and __weak ownership qualifiers exist. The former will not retain any object it points to, but leaves open the possibility of that object going away and it pointing to bad memory, whereas the latter doesn't retain the object and automatically sets itself to nil when its target is deallocated. Of the two, __weak is generally preferred on platforms that support it.

You would use these qualifiers for things like delegates, where you don't want the object to retain its delegate and potentially lead to a cycle.

Of course, there are several other less frequent, but still potentially problematic cases, which the published specification goes into in detail.

Much of the new behavior, based on keeping objects around as long as there is a strong pointer to them, is very similar to garbage collection on the Mac. However, the technical underpinnings are very different. Rather than having a garbage collector process that runs at regular intervals to clean up objects no longer being pointed to, this style of memory management relies on the rigid retain / release rules we all need to obey in Objective-C.

ARC simply takes the repetitive memory management tasks we've had to do for years and offloads them to the compiler so we never have to worry about them again. This way, you don't have the halting problems or sawtooth memory profiles experienced on garbage collected platforms. I've experienced both of these in my garbage collected Mac applications, and am eager to see how they behave under ARC.

For more on garbage collection vs. ARC, see this very interesting response by Chris Lattner on the Objective-C mailing list, where he lists many advantages of ARC over Objective-C 2.0 garbage collection. I've run into several of the GC issues he describes.

Why is UIBezierPath faster than Core Graphics path?

10 votes

I was playing around with drawing paths, and I noticed that in at least some cases, UIBezierPath outperforms what I thought would be a Core Graphics equivalent. The -drawRect: method below creates two paths: one UIBezierPath, and one CGPath. The paths are identical except for their locations, but stroking the CGPath takes roughly twice as long as stroking the UIBezierPath.

- (void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // Create the two paths, cgpath and uipath.
    CGMutablePathRef cgpath = CGPathCreateMutable();
    CGPathMoveToPoint(cgpath, NULL, 0, 100);

    UIBezierPath *uipath = [[UIBezierPath alloc] init];
    [uipath moveToPoint:CGPointMake(0, 200)];

    // Add 200 curve segments to each path.
    int iterations = 200;
    CGFloat cgBaseline = 100;
    CGFloat uiBaseline = 200;
    CGFloat xincrement = self.bounds.size.width / iterations;
    for (CGFloat x1 = 0, x2 = xincrement;
         x2 < self.bounds.size.width;
         x1 = x2, x2 += xincrement)
    {
        CGPathAddCurveToPoint(cgpath, NULL, x1, cgBaseline-50, x2, cgBaseline+50, x2, cgBaseline);
        [uipath addCurveToPoint:CGPointMake(x2, uiBaseline)
                  controlPoint1:CGPointMake(x1, uiBaseline-50)
                  controlPoint2:CGPointMake(x2, uiBaseline+50)];
    }
    [[UIColor blackColor] setStroke];
    CGContextAddPath(ctx, cgpath);

    // Stroke each path.
    [self strokeContext:ctx];
    [self strokeUIBezierPath:uipath];

    [uipath release];
    CGPathRelease(cgpath);
}

- (void)strokeContext:(CGContextRef)context
{
    CGContextStrokePath(context);
}

- (void)strokeUIBezierPath:(UIBezierPath*)path
{
    [path stroke];
}

Both paths use CGContextStrokePath(), so I created separate methods to stroke each path so that I can see the time used by each path in Instruments. Below are typical results (call tree inverted); you can see that -strokeContext: takes 9.5 sec., while -strokeUIBezierPath: takes only 5 sec.:

Running (Self)      Symbol Name
14638.0ms   88.2%               CGContextStrokePath
9587.0ms   57.8%                 -[QuartzTestView strokeContext:]
5051.0ms   30.4%                 -[UIBezierPath stroke]
5051.0ms   30.4%                  -[QuartzTestView strokeUIBezierPath:]

It looks like UIBezierPath is somehow optimizing the path that it creates, or I'm creating the CGPath in a naïve way. What can I do to speed up my CGPath drawing?

You are correct in that UIBezierPath is simply an objective-c wrapper for Core Graphics, and therefore will perform comparably. The difference (and reason for your performance delta) is your CGContext state when drawing your CGPath directly is quite different to that setup by UIBezierPath. If you look at UIBezierPath, it has settings for:

  • lineWidth,
  • lineJoinStyle,
  • lineCapStyle,
  • miterLimit and
  • flatness

When examining the call (disassembly) to [path stroke], you will note that it configures the current graphic context based on those previous values before performing the CGContextStrokePath call. If you do the same prior to drawing your CGPath, it will perform the same:

- (void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // Create the two paths, cgpath and uipath.
    CGMutablePathRef cgpath = CGPathCreateMutable();
    CGPathMoveToPoint(cgpath, NULL, 0, 100);

    UIBezierPath *uipath = [[UIBezierPath alloc] init];
    [uipath moveToPoint:CGPointMake(0, 200)];

    // Add 200 curve segments to each path.
    int iterations = 80000;
    CGFloat cgBaseline = 100;
    CGFloat uiBaseline = 200;
    CGFloat xincrement = self.bounds.size.width / iterations;
    for (CGFloat x1 = 0, x2 = xincrement;
         x2 < self.bounds.size.width;
         x1 = x2, x2 += xincrement)
    {
        CGPathAddCurveToPoint(cgpath, NULL, x1, cgBaseline-50, x2, cgBaseline+50, x2, cgBaseline);
        [uipath addCurveToPoint:CGPointMake(x2, uiBaseline)
                  controlPoint1:CGPointMake(x1, uiBaseline-50)
                  controlPoint2:CGPointMake(x2, uiBaseline+50)];
    }
    [[UIColor blackColor] setStroke];
    CGContextAddPath(ctx, cgpath);

    // Stroke each path
    CGContextSaveGState(ctx); {
        // configure context the same as uipath
        CGContextSetLineWidth(ctx, uipath.lineWidth);
        CGContextSetLineJoin(ctx, uipath.lineJoinStyle);
        CGContextSetLineCap(ctx, uipath.lineCapStyle);
        CGContextSetMiterLimit(ctx, uipath.miterLimit);
        CGContextSetFlatness(ctx, uipath.flatness);
        [self strokeContext:ctx];
        CGContextRestoreGState(ctx);
    }
    [self strokeUIBezierPath:uipath];

    [uipath release];
    CGPathRelease(cgpath);
}

- (void)strokeContext:(CGContextRef)context
{
    CGContextStrokePath(context);
}

- (void)strokeUIBezierPath:(UIBezierPath*)path
{
    [path stroke];
}

Snapshot from Instruments: Instruments snapshot showing equal performance

How to paint a good RGB color map?

8 votes

I'm trying to paint a full RGB color map that eventually will allow a user to select a color by tapping that visual map at any point. My current code is the following:

- (void)drawRect:(CGRect)rect
{
    CGContextRef c = UIGraphicsGetCurrentContext();

    for (float x=0; x<320; x++) {
        for (float y=0; y<416; y++) {

            float r = x / 320;
            float g = y / 416;
            float b = (y < 208) ? y / 208 : (416 - y) / 208;

            CGContextSetRGBFillColor(c, r, g, b, 1.0);
            CGContextFillRect(c, CGRectMake(x, y, 1, 1));

        }
    }
}

The result is not too bad but I'm not satisfied yet. The spectrum misses bright colors including white. The reason is clear: red, green and blue will never reach 1.0 at the same time.

Screenshot of the resulting color map

Do you have any advise how to improve that map so it represents the full spectrum of the RGB color space?

Thanks for all your input!

UPDATE:

As suggested by Josh Caswell I've used the HSB color space and the following code:

- (void)drawRect:(CGRect)rect
{
    CGContextRef c = UIGraphicsGetCurrentContext();

    int size = 20;

    for (float x=0; x<320; x+=size) {

        float s = x < 160 ? 1       : (320 - x) / 160;
        float b = x < 160 ? x / 160 : 1;

        for (float y=0; y<416; y+=size) {

            float h = y / 416;

            [[UIColor colorWithHue:h saturation:s brightness:b alpha:1.0] setFill];
            CGContextFillRect(c, CGRectMake(x, y, size, size));
        }
    }
}

This results in the following output which is perfect for my needs.

Result using HSB color space

Thanks for all input!

Well, you're trying to flatten a three-dimensional object onto a plane. An RGB color basically represents a point inside of a cube. Mapping the RGB solid onto a surface may be mathematically possible, but probably won't be particularly intuitive for this color-selection purpose.

You might have better luck using HSB, which is more like a cylinder with hue as angle around the circumference. You could "unroll" the cylinder, mapping hue to the y coordinate, then vary either brightness or saturation along the x axis -- you can do some simple testing to decide which component is more important for your application. A two-finger-swipe could be used for the third component if you felt it was necessary to include it.

HSB with saturation on X-axis:
HSB map; S on X axis

With brightness on X-axis:
HSB map; B on X axis

With saturation and brightness equal and both on X-axis:
HSB map; S=B on X axis

If you need white, which occurs when S = 0%, B = 100%, you could either stick a strip along one side, or try S = 1/B.

Xcode 4.2 - certificates ok, but "There is no codesign_wrapper executable. Please reinstall the Xcode developer tools." appears.

8 votes

I select "Build to Archive" in XCode 4.2, and the following error appears:

Validate "/Users/merlin/Motivapps/DerivedData/GoalWorkshop2-gzesusxljzqjkmbznfkkmylstlca/ArchiveIntermediates/GoalWorkshop2/InstallationBuildProductsLocation/Applications/Goal Wshop.app"
    cd /Users/merlin/Motivapps/GoalWorkshop2
    setenv PATH "/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin:/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin"
    setenv PRODUCT_TYPE com.apple.product-type.application
    /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/Validation "/Users/merlin/Motivapps/DerivedData/GoalWorkshop2-gzesusxljzqjkmbznfkkmylstlca/ArchiveIntermediates/GoalWorkshop2/InstallationBuildProductsLocation/Applications/Goal Wshop.app"

2011-06-10 17:10:48.879 Validation[15683:607] *** Warning: Defaulting to the standard codesign tool
warning: There is no codesign_wrapper executable. Please reinstall the Xcode developer tools. (-19058)
Unable to validate your application. - (null)

The same configuration worked well with previous XCode, and I just double checked - code sign certificates are valid. Suggestions?

Just change your selected Xcode version like this:

$ xcode-select -print-path 
/Xcode42
$ sudo xcode-select -switch /Xcode402
Password:
$ xcode-select -print-path           
/Xcode402

What does Objective-C actually do when you declare an object?

8 votes

I have read the memory management guide from Apple and I don't see where this case is explained...

Many times, especially when writing a class method to return an instance of a class, I'll start it out like this, because that's how I've seen it done, and it works.

[NOTE] This code is from memory - I'll update it when I get home to show an example that really works (I made this up to illustrate it, but obviously I don't recall it well enough to construct something that makes sense...

[EDIT] Here's my actual method - of course everyone was right that I must be calling alloc which I am.

+ (id)player
{
    Player *player = nil;
    if ((player = [[[super alloc] initWithFile:@"rocket.png"] autorelease])) {
    [player setProjectileType:kProjectileBullet];
        [player setProjectileLevel:1];
        [player setInvincible:YES];
        [player setEmitter:[CCParticleSystemQuad particleWithFile:@"exhaust.plist"]];
        [[player emitter] setPosition:ccp(0.0, player.contentSize.height/2)];
        [player addChild:player.emitter];
    }
    return player;
}

So what I got from the responses is: * Declaring the instance just gets me a pointer to a memory location and tells Xcode what class the object will be. * Setting the pointer to nil pretty much just sets it to zero - keeping it from having garbage in it (right?) * Since I'm autoreleasing the instance, the object that is returned is also autoreleased.

Thanks for helping me understand this!

Can someone explain what the compiler does when it sees this?

    DooDad* aDooDad = nil;

If you are really interested in what the compiler does, the answer is: the compiler will reserve some memory on the stack for the local variable aDooDad, which is a pointer type (it is generally 64 or 32 bits in size depending on the processor). That pointer is then initialized to contain nil (usually 0x00..00).

A statement like this:

         DooDad* aDooDad = [[DooDad alloc] init...];

makes use of pointer variable aDooDad to store the address in memory of the object that is further allocated (which is the address of memory reserved by alloc).

So, in the end,

    DooDad* aDooDad = nil;

is not declaring an object, just a variable whose content is interpreted as the address of an object of DooDad type. Such declaration, therefore, is just like any other declaration you know, e.g. when initializing an int to 0, so that later you can assign it some value in an if statement.

A statement like:

 [aDooDad doSomething];

is interpreted by the Objective-C runtime system like: send message doSomething to the object whose address is stored in aDooDad. If that address is nil no message is sent. On the other hand, if you dereference a nil pointer: *aDooDad you'll get undefined behavior.

Pointers are pretty low level stuff. I hope this helps.

Round to (1, 2, or 5) x 10^n in Objective-C?

8 votes

Is there a simple way of rounding a value either down to or to the nearest (1, 2, or 5) x 10^n where n is an integer? As in one of {..., .05 .1, .2, .5, 1, 2, 5, 10, 20, 50, 100...}

Thanks.

You can take the d=floor(log10(n)) of your number n to get the scale, then divide by 10 to the d to normalize the number to the range of [1.0,10.0). From that point it should be easy to round because there are very limited possibilities. Once you've done that, multiply by 10 to the d to restore the number to the original range.

The following function is in C, I don't know enough about Objective-C to know if this is idiomatic.

double RoundTo125(double value)
{
   double magnitude;
   magnitude = floor(log10(value));
   value /= pow(10.0, magnitude);
   if (value < 1.5)
      value = 1.0;
   else if (value < 3.5)
      value = 2.0;
   else if (value < 7.5)
      value = 5.0;
   else
      value = 10.0;
   value *= pow(10.0, magnitude);
   return value;
}