Chapter 8 Page 174 - dealloc and strong pointers


#1

Near the beginning of chapter 8 we read:

[quote]This is important: the notification center keeps strong references to its observers. If the object doesn’t remove itself as an observer before it is destroyed, then the next time a notification that the object registered for is posted, the center will try to send the object a message. Since that object no longer exists, your application will crash. Thus, if an object registers with the notification center, that object must unregister in its dealloc method.

- (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; }[/quote]
(My emphasis)

I’m confused about this and hoping somebody can straighten me out.

If the notification center keeps a strong reference to the observer, then dealloc won’t get called, at least by my reckoning, because its retained (reference count is at least 1). Checking my reasoning: if it were to keep a weak reference, the pointer would be automatically set to nil, and no bad behavior would result. However the behavior described in the book, that “Since your object no longer exists, your application will crash…” seems to describe the behavior of __unsafe_unretained, and not __strong.

What am I missing? Thanks in advance.


#2

After running some tests, it seems that calling addObserver:selector:name:object on the notification center does not retain the observer.

I believe that the book’s phrase, “…the notification center keeps strong references to its observers.” Should instead read, “…the notification center keeps __unsafe_unretained references to its observers.”

Page 174, Chapter 8, “Notification and Rotation”.

For reference, I ran this code:

@implementation RotationAppDelegate
{
    SomeObject* someObject;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    
    // Get the device object.
    UIDevice* device = [UIDevice currentDevice];
    
    // Tell it to start monitoring the accelerometer for orientation.
    [device beginGeneratingDeviceOrientationNotifications];
    
    // Get the notification center for the app.
    NSNotificationCenter* ncenter = [NSNotificationCenter defaultCenter];
    
    // Add self as observer.
    [ncenter addObserver:self
           selector:@selector(orientationChanged:)
               name:UIDeviceOrientationDidChangeNotification
             object:device];
    
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    
    someObject = [[SomeObject alloc] init];
    NSLog(@"Retain count: %ld", CFGetRetainCount((__bridge CFTypeRef)someObject)); // Check retain count.
    [ncenter addObserver:someObject
                selector:@selector(orientationChanged:)
                    name:UIDeviceOrientationDidChangeNotification
                  object:device];
    NSLog(@"Retain count: %ld", CFGetRetainCount((__bridge CFTypeRef)someObject)); // Check retain count again.
    
    return YES;
}

…and received the following output:

[quote]2012-11-13 13:16:07.839 HeavyRotation[15924:f803] Retain count: 1
2012-11-13 13:16:07.841 HeavyRotation[15924:f803] Retain count: 1
2012-11-13 13:16:07.843 HeavyRotation[15924:f803] Application windows are expected to have a root view controller at the end of application launch
2012-11-13 13:16:07.843 HeavyRotation[15924:f803] Orientation changed: 1
[/quote]

If I set the someObject pointer to nil without removing the observer, the app crashes as expected.

While checking retain counts in an ARC project might seem dodgy and unreliable, in the context of a single function call we should expect to see the retain count increase when an object is retained, and decrease when it’s released. This can be verified by creating a second reference pointer to the test object and checking the retain count again, i.e., SomeObject* anotherRef = someObject.

For example, changing the code to this:

someObject = [[SomeObject alloc] init]; NSLog(@"Retain count: %ld", CFGetRetainCount((__bridge CFTypeRef)someObject)); [ncenter addObserver:someObject selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:device]; NSLog(@"Retain count: %ld", CFGetRetainCount((__bridge CFTypeRef)someObject)); SomeObject* someObjectReference = someObject; NSLog(@"Retain count: %ld", CFGetRetainCount((__bridge CFTypeRef)someObject)); someObjectReference = nil; NSLog(@"Retain count: %ld", CFGetRetainCount((__bridge CFTypeRef)someObject));

…produced the following output:

[quote]2012-11-13 13:25:09.270 HeavyRotation[15965:f803] Retain count: 1
2012-11-13 13:25:09.272 HeavyRotation[15965:f803] Retain count: 1
2012-11-13 13:25:09.272 HeavyRotation[15965:f803] Retain count: 2
2012-11-13 13:25:09.273 HeavyRotation[15965:f803] Retain count: 1
2012-11-13 13:25:09.274 HeavyRotation[15965:f803] Application windows are expected to have a root view controller at the end of application launch
2012-11-13 13:25:09.275 HeavyRotation[15965:f803] Orientation changed: 1[/quote]

Note: The class SomeObject is just an NSObject derived class that contains a selector for the NSNotificationCenter to call into:

// SomeObject.h
#import <Foundation/Foundation.h>

@interface SomeObject : NSObject

@end

// SomeObject.m
@implementation SomeObject

- (void)orientationChanged:(NSNotification*)note
{
    return;
}

Hope this helps.


#3

I just found out that this was addressed in the 2nd printing: bignerdranch.com/documents/i … errata.pdf

Thanks, and it’s good to know I’m not crazy.


#4

This was probably the worst typo ever. Putting the emphasis tags on the exact opposite… we still laugh about this.