Recently I am working on migrate an old and complex project to ARC, I have learned some knowledges about ARC and reprinted some useful topics before, but I still have some barriers. After I solve these issues, I decide to write it down as a record. Not every tip is written by myself, some tips are from internet. Although I have the same problems as others, but I think their language description is better than me. If I find any new tips about ARC I will update to this article.
First, before we start to migrate a project to ARC, we should learn some basic knowledge about ARC. I list some useful website here.
1. Migrating your code to Objective-C ARC
http://blog.mugunthkumar.com/articles/migrating-your-code-to-objective-c-arc/
2. Understanding Automatic Reference Counting in Objective-C
http://longweekendmobile.com/2011/09/07/objc-automatic-reference-counting-in-xcode-explained/
3. Beginning ARC in iOS 5 Tutorial Part 1
http://www.raywenderlich.com/5677/beginning-arc-in-ios-5-part-1
4.Beginning ARC in iOS 5 Tutorial Part 2
http://www.raywenderlich.com/5773/beginning-arc-in-ios-5-tutorial-part-2
5. If you want to learn more detail about ARC in LLVM, you should take a look at LLVM documentation about ARC.
http://clang.llvm.org/docs/AutomaticReferenceCounting.html
Migration Woes
1. Receiver type ‘X’ for instance message is a forward declaration
If you have a class, let’s say MyView that is a subclass of UIView, and you call a method on it or use one of its properties, then you have to #import the definition for that class. That is usually a requirement for getting your code to compile in the first place, but not always.
For example, you added a forward declaration in your .h file to announce that MyView is class:
@class MyView;
|
Later in your .m file you do something like:
[myView setNeedsDisplay]; |
Previously this might have compiled and worked just fine, even without an #import statement. With ARC you always need to explicitly add an import:
#import "MyView.h"
2. Switch case is in protected scope
You get this error when your code does the following:
switch (X) { case Y: NSString *s = ...; break; } |
This is no longer allowed. If you declare new pointer variables inside a case statement you must enclose the whole thing in curlies:
switch (X) { case Y: { NSString *s = ...; break; } } |
Now it is clear what the scope of the variable is, which ARC needs to know in order to release the object at the right moment.
3. A name is referenced outside the NSAutoreleasePool scope that it was declared in
You may have some code that creates its own autorelease pool:
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; // . . . do calculations . . . NSArray* sortedResults = [[filteredResults sortedArrayUsingSelector:@selector(compare:)] retain]; [pool release]; return [sortedResults autorelease]; |
The conversion tool needs to turn that into something like this:
@autoreleasepool { // . . . do calculations . . . NSArray* sortedResults = [filteredResults sortedArrayUsingSelector:@ selector(compare:)]; } return sortedResults; |
But that is no longer valid code. The sortedResults variable is declared inside the @autoreleasepool scope and is therefore not accessible outside of that scope. To fix the issue you will need to move the declaration of the variable above the creation of the NSAutoreleasePool:
NSArray* sortedResults; NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; . . . |
Now the conversion tool can properly rewrite your code.
4. ARC forbids Objective-C objects in structs or unions
One of the restrictions of ARC is that you can no longer put Objective-C objects inside C structs. The following code is no longer valid:
typedef struct { UIImage *selectedImage; UIImage *disabledImage; } ButtonImages; |
You are recommended to replace such structs with true Objective-C classes instead. We’ll talk more about this one later, and then I’ll show you some other workarounds.
5. Init methods must return a type related to the receiver type
init methods must be instance methods and must return an Objective-C pointer type. Additionally, a program is ill-formed if it declares or contains a call to an init method whose return type is neither idnor a pointer to a super-class or sub-class of the declaring class (if the method was declared on a class) or the static receiver type of the call (if it was declared on a protocol).
Rationale: there are a fair number of existing methods with init-like selectors which nonetheless don't follow the init conventions. Typically these are either accidental naming collisions or helper methods called during initialization. Because of the peculiar retain/release behavior of init methods, it's very important not to treat these methods as init methods if they aren't meant to be. It was felt that implicitly defining these methods out of the family based on the exact relationship between the return type and the declaring class would be much too subtle and fragile. Therefore we identify a small number of legitimate-seeming return types and call everything else an error. This serves the secondary purpose of encouraging programmers not to accidentally give methods names in the init family.
Note that a method with an init-family selector which returns a non-Objective-C type (e.g. void) is perfectly well-formed; it simply isn't in the init family.
6. Cast of Objective-C pointer type ‘NSString *’ to C pointer type ‘CFStringRef(aka ‘const struct __CFString *’)’
You'll have to use __bridge
or one of the other keywords.
This is because Core Foundation objects (CF*Refs) are not controlled by ARC, only Obj-C objects are. So when you convert between them, you have to tell ARC about the object's ownership so it can properly clean them up. The simplest case is a __bridge
cast, for which ARC will not do any extra work (it assumes you handle the object's memory yourself).
7. Multiple methods named 'handleResponseData:' found with mismatched result, parameter type or attributes
For this situation, there are two methods called handleResonseData, but they have different parameters.
- (void)handleResponseData:(NSString *) data; - (void)handleResponseData:(NSData *) data;
If we call the first method use a string type parameter or call the second method use a data type parameter, no problem.
Another way to call handleResonseData method:
[instance performSelector:@selector(handleResponseData:) withObject:object];
If we use this way to call handleResonseData, the compiler will never know which method should be called.
8. Assigning retained object to unsafe_unretained variable; object will be released after assignment
It's the same situation as assigning retained object to weak. You should use strong variable instead of unsafe_unretained.
Another situation about assigning retained object to weak.
__weak NSString *str = [[NSString alloc] initWithFormat:...]; NSLog(@"%@", str); // will output "(null)"
There is no owner for the string object (because str is weak) and the object will be deallocated immediately after it is created. Xcode will give a warning when you do this because it’s probably not what you intended to happen (“Warning: assigning retained object to weak variable; object will be released after assignment”).
You can use the __strong keyword to signify that a variable is a strong pointer:
__strong NSString *firstName = self.textField.text; |
But because variables are strong by default this is a bit superfluous.
9. [rewriter] it is not safe to remove 'release' message on a global variable
10. 'release' is unavailable: not available in automatic reference counting mode
11. ARC forbids explicit message send of 'release'
Number 9, 10, 11, these three problem are appear at the same time. Remove all retain release autorelease NSAutoReleasePools and retainCount calls, since ARC makes them for you. With the exception of NSAutoReleasePools They have been replaced by @autorelease{}.
12. The current deployment target does not support automated __weak references
Because ARC is largely a new feature of the LLVM 3.0 compiler and not of iOS 5, you can also use it on iOS 4.0 and up. The only part of ARC that does require iOS 5 are the weak pointers. That means if you wish to deploy your ARC app on iOS 4, you cannot use weak properties or __weak variables.
You don’t need to do anything special to make your ARC project work on iOS 4. If you choose a version of iOS 4 as your Deployment Target, then the compiler will automatically insert a compatibility library into your project that makes the ARC functionality available on iOS 4. That’s it, just pick iOS 4.x as the Deployment Target and you’re done.
If you use weak references anywhere in your code, the compiler will give the following error:
"Error: the current deployment target does not support automated __weak references"
|
You cannot use weak or __weak on iOS 4, so replace weak properties with unsafe_unretained and __weak variables with __unsafe_unretrained. Remember that these variables aren’t set to nil when the referenced object is deallocated, so if you’re not careful your variables may be pointing at objects that no longer exist. Be sure to test your app with NSZombieEnabled!
13. Variable is not assignable (missing __block type specifier)
please read Blocks and Variables in Blocks Programming Topic (http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/bxVariables.html)
If you defined a variable out of a block, at the same time you want use this variable in block, you should add __block for the variable.
14. PerformSelector may cause a leak because its selector is unknown
We now know that Objective-C ARC compiler knows more Objective-C than you. This error message is because of that. The ARC compiler tries to identify the method family and determine whether to add a retain or release to the returned value from the caller code. This means, if your method starts with init, alloc, copy, mutableCopy or new, the ARC compiler will add a release to the calling code after the variable scope ends. Since are using a selector to call a method dynamically at runtime, ARC doesn’t really know if the method called returns a +1 retained object or a auto-released object. As such, ARC cannot reliably insert a retain or release to the returned object after its scope ends. This warning is shown to warn you of potential memory leaks.
If you are sure that your code works fine without memory leaks, you can ignore this warning. To suppress this warning, you can turn off the compiler flag -Warc-performSelector-leaks warning on a line by line basis like this.
#pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [self performSelector:self.mySel]; #pragma clang diagnostic pop |
Unfortunately, you cannot annotate a dynamic selector using __attribute__ ((objc_method_family(*))).
15. Capturing 'request' strongly in this block is likely to lead to a retain cycle
My understanding of the documentation says that using keyword block
and setting the variable to nil after using it inside the block should be ok, but it still shows the warning.
__block ASIHTTPRequest*request =[[ASIHTTPRequest alloc] initWithURL:... [request setCompletionBlock:^{ NSDictionary*jsonDictionary =[[CJSONDeserializer deserializer] deserialize:request.responseData error:nil]; request = nil; // .... }];
Update: got it to work with the keyword '_weak' instead of '_block', and using a temporary variable:
ASIHTTPRequest*_request =[[ASIHTTPRequest alloc] initWithURL:... __weak ASIHTTPRequest*request = _request; [request setCompletionBlock:^{ NSDictionary*jsonDictionary =[[CJSONDeserializer deserializer] deserialize:request.responseData error:nil]; // ... }];
If you want to also target iOS 4, use __unsafe_unretained
instead of __weak
. Same behavior, but the pointer stays dangling instead of being automatically set to nil when the object is destroyed.
16. Sending 'UIButton *__strong' to parameter of incompatible type 'id<NSCopying>'
NSMutableDictionary *dict = [NSMutableDictionary dictionary]; NSString *object = @"object"; UIButton *button = [UIbutton buttonWithType:UIButtonTypeCustom]; [dict setObject:object forKey:button];
For this situation, I use a button as key of dict. But for NSMutableDictionary, when we call - (void)setObject:(id)anObject forKey:(id < NSCopying >)aKey, the key should be a member of NSCopying class. My solution is that write a custom button inherit UIButton and implement NSCopying. Then use custom button instead of UIButton.