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:
NSString *string = nil; NSArray *components = [NSArray arrayWithObjects:@"one",@"two",nil]; 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 NS_RETURNS_RETAINED, which means your interface would look like the following:
- (id)MCSM_initWithComponents:(NSArray *)components seperatedByString:(NSString *)seperator NS_RETURNS_RETAINED; |
Instead of NS_RETURNS_RETAINED you can also use __attribute__((ns_returns_retained)), 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__((ns_consumes_self)) in conjunction __attribute__((ns_returns_retained)), 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.
Good post. I just want to mention that you could work around this by suffixing your category methods instead of prefixing them. This also improves the auto completion…
Good post. I just want to mention that you could work around this by suffixing your category methods instead of prefixing them. This also improves the auto completion…
+1
Agreed you could suffix your category methods, but that is obviously just a matter of style