NSNotificationCenter removeObserver: in dealloc and thread-safety

前端 未结 3 1856
别跟我提以往
别跟我提以往 2021-02-03 14:12

I\'m using ARC and I\'m calling [[NSNotificationCenter defaultCenter] removeObserver:someObserver]; in observer\'s dealloc.

From NSNotification

相关标签:
3条回答
  • 2021-02-03 14:46

    I just stumbled into this problem myself: I had one notification just in the process of being sent (which always happens in the main thread) while the object was in the process of being deallocated from a background thread. I fixed it by simply performing removeObserver in the main thread and waiting:

    - (void)removeNotificationCenterObserver
    {
        NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
        [notificationCenter removeObserver:self];
    }
    
    - (void)dealloc
    {
        [self performSelectorOnMainThread:@selector(removeNotificationCenterObserver) withObject:self waitUntilDone:YES];
    }
    

    This waits until the current run loop cycle ends and executes this message at the beginning of the next run loop cycle. This ensures that any functions that are still running will finish.

    0 讨论(0)
  • 2021-02-03 14:46

    Yes, NSNotificationCenter doesn't retain observer, but it still has a pointer to it in it's dispatch table.

    Q1: Quoting Apple docs

    Regular notification centers deliver notifications on the thread in which the notification was posted. Distributed notification centers deliver notifications on the main thread. At times, you may require notifications to be delivered on a particular thread that is determined by you instead of the notification center. For example, if an object running in a background thread is listening for notifications from the user interface, such as a window closing, you would like to receive the notifications in the background thread instead of the main thread. In these cases, you must capture the notifications as they are delivered on the default thread and redirect them to the appropriate thread.

    Q2,3: Yes.

    Q4,5: AFAIK it's safe unless you stumble into circular reference. I usually add/remove in -viewWillAppear:/-viewWillDisappear: for UIViewControllers and -init/dealloc for other classes.

    0 讨论(0)
  • 2021-02-03 14:46

    I've wondered the same thing, and I can't find it documented. Here's what I think is going on.

    removeObserver: is not thread safe in the way that you want it to be.

    Think about the following situation. The last reference to the observer is released while executing code on thread A. Thread A will call the observer's dealloc method. At the same time, the observed object executes a [NSNotificcationCenter postNotificationName:object:] from thread B. This leads to an unavoidable race condition. That is, a notification will be in flight while your object is within its dealloc method.

    - (void)init {
        ...
        [[NSNotificcationCenter defaultCenter] addObserver:self
                                                  selector:@selector(callback:)
                                                      name:@"whatever"
                                                    object:nil];
        ...
    }
    
    - (void)dealloc {
    
        // If the observed object posts the notification on thread B while 
        // thread A is here, there's a race! At best, thread B will be
        // in callback: while thread A is here in dealloc. That's probably
        // not what you expect. The worst case is that thread B won't make
        // make it to callback: until after thread A completes the dealloc
        // and the memory has been freed. Likely crash!
    
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    
        // If the observed object does the post when thread A is here,
        // you'll be fine since the observation has been removed.
    }
    

    This isn't a problem for main thread objects that are only observing other main thread objects since, by definition, you can't get into the thread A and B scenario I described.

    For multi-threaded cases, the only way to guarantee you'll avoid the problem is to ensure that the observation stops before the observer's refcount hits 0. If someone else is responsible for the observer's lifetime (i.e. you have any sort of term or close method), it's easy. If not, I don't know of a solution.

    0 讨论(0)
提交回复
热议问题