And why can it vary between Debug/Release/Simulator/Device combinations? (Sample code below.)
I inherited some code that "works" in the simulator and on devices in Debug, but not on a device in Release (used to with last LLVM). It's related to ARC and a weak property (which should have been strong), but I don't understand fully what's happening and am hoping someone can explain it to me and why it varies between Debug/Release/Device/Simulator.
I created a simple sample project that demonstrates this, with all code in AppDelegate.m as included below. When run in the simulator, the logged output is:
-[BaseThingMaker baseMakeThing]: returning a Thing
-[ThingMaker makeThing]: returning a Thing
-[AppDelegate application:didFinishLaunchingWithOptions:]: got a Thing
When run on the device, the logged output is:
-[BaseThingMaker baseMakeThing]: returning a Thing
-[ThingMaker makeThing]: returning a (null)
-[AppDelegate application:didFinishLaunchingWithOptions:]: got a (null)
On a device (in Debug or Release), the weak self.thing
in ThingMaker
is null even for the log message before the method is complete. On the simulator, the self.thing
object is returned.
Sample code from Single Window App (AppDelegate.m):
#import "AppDelegate.h"
@interface Thing : NSObject
@end
@implementation Thing
@end
@interface BaseThingMaker : NSObject
-(Thing *)baseMakeThing;
@end
@implementation BaseThingMaker
-(Thing *)baseMakeThing
{
Thing *thing = [Thing new];
NSLog(@"%s: returning a %@", __PRETTY_FUNCTION__, NSStringFromClass([thing class]));
return thing;
}
@end
@interface ThingMaker : NSObject
- (id)makeThing;
@end
@interface ThingMaker ()
@property (weak, nonatomic) id thing;
@end
@implementation ThingMaker
- (id)makeThing
{
BaseThingMaker *baseThingMaker = [BaseThingMaker new];
self.thing = [baseThingMaker baseMakeThing];
NSLog(@"%s: returning a %@", __PRETTY_FUNCTION__, NSStringFromClass([self.thing class]));
return self.thing;
}
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
ThingMaker *thingMaker = [ThingMaker new];
id thingIWant = [thingMaker makeThing];
NSLog(@"%s: got a %@", __PRETTY_FUNCTION__, NSStringFromClass([thingIWant class]));
return YES;
}
@end
The method
baseMakeThing
does not return an object its call owns, in this case it returns an autoreleased object. If it is an autoreleased object it will have an owner - the autorelease pool - until the pool it emptied when the run loop cycles around. So the assignment to the weak property will be valid until that happens, and the code will appear to work...Unless the compiler optimises, instead of retaining an autoreleased object the compiler can sometimes pull the object out of the autorelease pool and take ownership that way. If the compiler does this then immediately after the assignment to the weak property the compiler can relinquish ownership, as the reference is not used later in
makeThing
, and this will in turn null the weak property...The differences you are seeing are down to how much optimisation the compiler is doing depending on the type of build.