Question

I have a situation in which it can happen, that the last strong reference to an observer is removed while the observer processes an incoming notification.

That leads to the observer being deallocated immediately. I would normally expect, that a currently running method can finish before an object is deallocated. And this is what happens during normal message dispatch.

A simplified version of the code:

TKLAppDelegate.h:

#import <UIKit/UIKit.h>
#import "TKLNotificationObserver.h"

@interface TKLAppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) TKLNotificationObserver *observer;

@end

TKLAppDelegate.m:

#import "TKLAppDelegate.h"

@implementation TKLAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

  // Create an observer and hold a strong reference to it in a property
  self.observer = [[TKLNotificationObserver alloc] init];

  // During the processing of this notification the observer will remove the only strong reference
  // to it and will immediatly be dealloced, before ending processing.
  [[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationName" object:nil];

  // Create an observer and hold a strong reference to it in a property
  self.observer = [[TKLNotificationObserver alloc] init];

  // During the manual calling of the same method the observer will not be dealloced, because ARC still
  // holds a strong reference to the message reciever.
  [self.observer notificationRecieved:nil];

  return YES;
}

@end

TKLNotificationObserver.m:

#import "TKLNotificationObserver.h"
#import "TKLAppDelegate.h"

@implementation TKLNotificationObserver

- (id)init {
  if (self = [super init]) {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationRecieved:) name:@"NotificationName" object:nil];
  }
  return self;
}

- (void)notificationRecieved:(NSNotification *)notification {
  [self doRemoveTheOnlyStrongReferenceOfThisObserver];
  NSLog(@"returing from notification Observer");
}

- (void)doRemoveTheOnlyStrongReferenceOfThisObserver {
  TKLAppDelegate * delegate = [[UIApplication sharedApplication] delegate];
  delegate.observer = nil;
}

- (void)dealloc {
  [[NSNotificationCenter defaultCenter] removeObserver:self name:@"NotificationName" object:nil];
  NSLog(@"dealloc was called");
}

@end

Using the App Delegate in this way is no good style and only done for demonstration purposes, the real code does not involve the app delegate.

The output is:

 dealloc was called
 returing from notification Observer
 returing from notification Observer
 dealloc was called

That is in the first case dealloc is called before the notification processing finished. In the second case it behaves as I expected.

If I keep a strong reference to self inside notificationReceived the dealloc only happens after the processing. My expectation was, that ARC, the runtime or whoever else keeps this strong reference for me.

What is wrong with my code? Or is something wrong with my expectation? Is there any Apple- or Clang-provided documentation on this?

Was it helpful?

Solution

My expectation was, that ARC, the runtime or whoever else keeps this strong reference for me.

That is not the case, as documented in the Clang/ARC documentation:

The self parameter variable of an Objective-C method is never actually retained by the implementation. It is undefined behavior, or at least dangerous, to cause an object to be deallocated during a message send to that object.

Therefore, if calling doRemoveTheOnlyStrongReferenceOfThisObserver can have the side-effect of releasing self, you would have to use an temporary strong reference to avoid deallocation:

- (void)notificationRecieved:(NSNotification *)notification {
   typeof(self) myself = self;
   [self doRemoveTheOnlyStrongReferenceOfThisObserver];
   NSLog(@"returing from notification Observer");
}

A better solution would probably to avoid this side-effect.

OTHER TIPS

the first dealloc probably happens as you set the observer property of the appDelegate twice and therefore the first instance is dealloced as soon as you set it the second time

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