Keeping the Static Analyzer Happy: Prefixed Initializers

The latest version of Xcode ships with LLVM 3.0 as it’s default compiler, and one of the first things that you will notice is that is a lot more thorough when it analyses your code compared to previous versions (which can only be a good thing). One thing that the static analyser now warns you about, is that you are over releasing objects that are returned from prefixed intalizer methods (init), such as in a category (for my previous posts on categories see here and here).

For example my NSString category has the following method:

- (id)MCSM_initWithComponents:(NSArray *)components 
seperatedByString:(NSString *)seperator;

And it’s implementation looks like this:


- (id)MCSM_initWithComponents:(NSArray *)components seperatedByString:(NSString *)seperator {

    NSMutableString *componentizedString = [NSMutableString string];

    NSUInteger i = 0;
    for(NSString *component in components) {

            if (i == 0) {
                    [componentizedString appendString:component];
            } else {
                    [componentizedString appendFormat:@"%@%@",seperator,component];
            }

            i++;
    }
    return [self initWithString:componentizedString];
}

This method takes an array of strings, and joins together using the separator parameter and can be used in the following way:

NSArray *components = [NSArray arrayWithObjects:@"one", @"two", nil]; 
NSString *string = [[NSString alloc] MCSM_initWithComponents:components seperatedByString:@","];
NSLog(@"%@",string);
[string release];

This would output:

one,two

As this method is in a category of NSString I don’t want it to clash with any other implementations. The common practise in Objective-C is to prefix categories methods (due to the lack of namespaces), so I have with MCSM_. The issue is that the static analyser will now think that this method returns an autoreleased object, as the method does not begin with init, new, copy or alloc. This means when you release the object the static analyser will complain about you over releasing an object.

So how do you fix this?

To fix this you can tell the compiler that the method returns a retained object by using the source annotation NSRETURNSRETAINED, which means your interface would look like the following:

- (id)MCSM_initWithComponents:(NSArray *)components 
seperatedByString:(NSString *)seperator NS_RETURNS_RETAINED;

Instead of NSRETURNSRETAINED you can also use __attribute((nsreturnsretained)), which is a longer way of writing the same thing:

- (id)MCSM_initWithComponents:(NSArray *)components 
seperatedByString:(NSString *)seperator __attribute__((ns_returns_retained));

So thats all fixed? Unfortunately not quite yet. The static analyser will now complain about a memory leak, as we have allocated a NSString by doing [NSString alloc], but then it isn’t referenced again in our code. For a method that begins with init, the static analyser knows that the method consumes the variable (which means it releases the parameter upon completion), and that is the behaviour we need.

To do this we have to use the source annotation __attribute((nsconsumesself)) in conjunction __attribute((nsreturnsretained)), which means your interface will look like:

- (id)MCSM_initWithComponents:(NSArray *)components 
seperatedByString:(NSString *)seperator __attribute__((ns_consumes_self))__attribute__((ns_returns_retained));

And that will fix it.

Summary

You don’t need to use the source annotations if your code obeys the Objective-C naming conventions, but in certain circumstances like the one above you need to help the Static Analyser do its job. As LLVM forms the the backbone of Automatic Reference Counting (ARC), you still need to do this even if your not retaining and releasing memory yourself.