Question

I was trying to prepare a simple demo for using NSNotificationCenter when I got unexpected crashes, but only sometimes. I have two classes, Scout and Shouter. On -init, Shouter posts a notification to NSNotificationCenter, and Scout adds itself as an observer for the same notification on its respective -init.

When the application crashes, it has "unrecognized selector sent to instance 0x100109480" (address changes of course). This led me to check the addresses of the objects to see what happens. It appears that sometimes a Shouter init after a Scout init is overwriting the address for the Scout object.

Code is below. I'm hoping to understand why this happens. I've tried removing the id/idCounter mechanism and using a hard-coded int in case my understanding of static was wrong.

Relevant code for Shouter:

@implementation Shouter{
    int _id;    //An ID-code to recognize different instances of Shouter.
}

-(id)init{

    NSLog(@"init test Shouter - what is self? %@", self);

    if(self=[super init]){
        static int _idCounter = 0;
        _id = _idCounter++;
        NSDictionary *dictionary = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:_id] forKey:@"ID-code"];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"sample" object:nil userInfo:dictionary];
    }
    return self;
}

@end

Relevant code for Scout:

@implementation Scout
-(id)init{    
    NSLog(@"init test Scout - what is self? %@", self);    
     if(self=[super init]){
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(messageReceived:)
                                                     name:@"sample"
                                                   object:nil];
    }    
    return self;
}

- (void) messageReceived:(NSNotification*)notification{
    NSDictionary *dictionary = [notification userInfo];
    id object = [dictionary objectForKey:@"ID-code"];
    int code = -1;
    if([object isKindOfClass:[NSNumber class]]){
        code = [((NSNumber*)object) intValue];
    }

    NSLog(@"Received a message from a Shouter instance.  ID-code was: %i", code);
}

@end

Relevant code for main:

int main(int argc, const char * argv[])
{
    @autoreleasepool {        
        [[Shouter alloc] init];
        [[Scout alloc] init];
        [[Shouter alloc] init];        
    }
    return 0;
}

Example output in crash instance:

2014-01-23 11:38:30.800 Lab 3 Snippets[3461:303] init test Shouter - what is self? <Shouter: 0x100109480>
2014-01-23 11:38:30.802 Lab 3 Snippets[3461:303] init test Scout - what is self? <Scout: 0x1003007b0>
2014-01-23 11:38:30.803 Lab 3 Snippets[3461:303] init test Shouter - what is self? <Shouter: 0x1003007b0>
2014-01-23 11:38:30.803 Lab 3 Snippets[3461:303] -[Shouter messageReceived:]: unrecognized selector sent to instance 0x1003007b0
Was it helpful?

Solution

You should always unregister notifications when your object is deallocated, e.g:

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"sample" object:nil];
    [super dealloc]; // Skip this if you're using ARC
}

What might happen is that you're Scout object is deallocated right after its init method finishes, and the next Shooter object is allocated at the same address. NSNotificationCenter doesn't know this has happened, and tries to call messageReceived: on the former Scout object. Instead it finds a Shooter object that does not respond to the method, hence your error.

NSNotificationCenter never retains objects registered as observers, because that would create a lot of retain cycles that would be really hard to work around. Make sure something holds on to your objects for as long as you need them to exist.

OTHER TIPS

In guessing ARC is on. ARC will see that the object is never used again and release it immediately, but since you never unregister the notification subscription, the notification center tries to talk to the new object that's allocated in its place.

You can also use [[NSNotificationCenter defaultCenter] removeObserver:self];on dealloc method it will remove all references of self on the Notification Center.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top