Reputation: 5366
I'm refreshing my knowledge in Objective-C world and now I'm testing some ARC with __weak
local variables.
I have very simple code with such files GAObject.h
#import <Foundation/Foundation.h>
@interface GAObject : NSObject
+ (instancetype)create;
@end
Implementation of this interface GAObject.h
#import "GAObject.h"
@implementation GAObject
+ (instancetype)create {
return [[GAObject alloc] init];
}
- (void)dealloc {
NSLog(@"GAObject is being deallocated");
}
@end
So there is simple factory method create
and I override dealloc
method to watch if objects was deallocated when I expecting this. Now the funny part main.m
:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "Learning/GAObject.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"1");
NSObject *o1 = [[GAObject alloc] init];
NSObject * __weak weakObject = o1; // Line 1
o1 = nil; // o1 should be deallocated because there is no strong references pointing to o1.
NSLog(@"2");
NSObject *o2 = [GAObject create]; // Line 2
o2 = nil; // o2 should be deallocated here too but it is not deallocated. Why?
NSLog(@"3");
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
In the output I see this:
1
GAObject is being deallocated
2
3
But My expecting results should be:
1
GAObject is being deallocated
2
GAObject is being deallocated
3
If I create o2
using factory method then I have this behaviour. If I create o2
like this: [[GAObject alloc] init]
then I get expected output. Also I noticed that when I remove line with weakObject
I also get expected results. Can somebody explain it?
Upvotes: 0
Views: 56
Reputation: 90521
It's because ARC still respects the Cocoa memory-management naming conventions.
Under those conventions, a method named +create
returns a +0 reference. So, in the implementation of the method, ARC has to balance the +1 reference of the alloc
/init
pair by autoreleasing the reference.
Then, in main()
, ARC has to assume it has received a +0 reference from the call to +create
. If it needed the reference to survive the current scope, it would retain it, but it doesn't so it doesn't. The second GAObject
instance would be deallocated when the autorelease pool is drained, but that will never happen because UIApplicationMain()
never returns. If you use two separate autorelease pools, one for the code dealing with GAObject
s and another for the call to UIApplicationMain()
, I expect you'll get the result you expect.
If ARC did need the reference to survive, it would retain at the assignment to the strong variable and release when that variable is assigned a new value (including nil
) or goes out of scope. ARC has a run-time optimization that an autorelease-return in a callee and a retain of the returned value in the caller cancel each other out, such that the object is never put in the autorelease pool. If this were happening, you would get your expected results.
In fact, my expectation is that the compiler initially would emit that retain and release even in your case, but a subsequent pass removes redundant retains and releases. Your example has the release immediately follow the retain, which makes it even more obvious to the compiler that the pair are redundant. Because the retain gets removed, that autorelease optimization doesn't kick in, and a reference to your object really does get put into the autorelease pool.
If your method were named +newGAObject
, then the naming conventions would mean it returns a +1 reference and this all changes. (Of course, as it stands, your +create
method is just doing the same thing as the built-in +new
method, except for the autorelease that ARC has to add. So, you could just change the calling code to use +new
and that would also sidestep this issue.)
I don't know why the line with weakObject
matters. But, since the behavior you're seeing depends on certain optimizations, anything that could tweak the optimizations can change the outcome.
Upvotes: 2