NSNotificationCenter源码学习

  • 之前在看重复的NSTimer在加到runloop之后,发现由于runloop会强持有observer,导致在dealloc中去invalidate不会起作用的问题;联想到通知也是addObserver,我们一般都是在dealloc中去removeObserver,而observer的dealloc可以正常调用,就想了解一下这其中的不同

  • addObserver,不remove也不会造成observer的泄漏,但是会有野指针的问题,为什么?

  • addObserver多次一个observer,通知会回调多次,为什么?

一般我们都会要在dealloc中去removeObserver,iOS9之后则不需要,系统帮我们处理了
带着这样的疑问,通过GNU的源码来看一下内部的实现

addObserver

- (void) addObserver: (id)observer
            selector: (SEL)selector
                name: (NSString*)name
              object: (id)object
{
    IMP     method;
    Observation *list;
    Observation *o;
    GSIMapTable m;
    GSIMapNode  n;
    
    if (observer == nil)
        [NSException raise: NSInvalidArgumentException
                    format: @"Nil observer passed to addObserver ..."];
    
    if (selector == 0)
        [NSException raise: NSInvalidArgumentException
                    format: @"Null selector passed to addObserver ..."];
    
#if defined(DEBUG)
    if ([observer respondsToSelector: selector] == NO)
        NSLog(@"Observer '%@' does not respond to selector '%@'", observer,
              NSStringFromSelector(selector));
#endif
    
    method = [observer methodForSelector: selector];
    if (method == 0)
        [NSException raise: NSInvalidArgumentException
                    format: @"Observer can not handle specified selector"];
    
    lockNCTable(TABLE);
    
    o = obsNew(TABLE, selector, method, observer);
    
    if (object != nil)
    {
        object = CHEATGC(object);
    }
    
    /*
     * Record the Observation in one of the linked lists.
     *
     * NB. It is possible to register an observer for a notification more than
     * once - in which case, the observer will receive multiple messages when
     * the notification is posted... odd, but the MacOS-X docs specify this.
     */
    
    if (name)
    {
        /*
         * Locate the map table for this name - create it if not present.
         */
        n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
        if (n == 0)
        {
            m = mapNew(TABLE);
            /*
             * As this is the first observation for the given name, we take a
             * copy of the name so it cannot be mutated while in the map.
             */
            name = [name copyWithZone: NSDefaultMallocZone()];
            GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
            GS_CONSUMED(name)
        }
        else
        {
            m = (GSIMapTable)n->value.ptr;
        }
        
        /*
         * Add the observation to the list for the correct object.
         */
        n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
        if (n == 0)
        {
            o->next = ENDOBS;
            GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);
        }
        else
        {
            list = (Observation*)n->value.ptr;
            o->next = list->next;
            list->next = o;
        }
    }
    else if (object)
    {
        n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
        if (n == 0)
        {
            o->next = ENDOBS;
            GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);
        }
        else
        {
            list = (Observation*)n->value.ptr;
            o->next = list->next;
            list->next = o;
        }
    }
    else
    {
        o->next = WILDCARD;
        WILDCARD = o;
    }
    
    unlockNCTable(TABLE);
}

简化一下流程:

  • 异常判断处理
  • 创建observer实例o = obsNew(TABLE, selector, method, observer)
  • 添加observer到list中

具体的过程可参照下图理解


NSNotificationCenter源码学习_第1张图片
图片.png

由于通知内部存储observation实例用的是map,observation实例存放在链表中,没有去重的处理,所以添加一个观察者,多次那么久会在mapTable中存在多个,发送通知的时候也会触发多次

obsNew

看看obsNew内部是如何创建observation实例的

static Observation *
obsNew(NCTable *t, SEL s, IMP m, id o)
{
    Observation *obs;
    
#if __OBJC_GC__
    
    /* With clang GC, observations are garbage collected and we don't
     * use a cache.  However, because the reference to the observer must be
     * weak, the observation has to be an instance of a class ...
     */
    static Class observationClass;
    
    if (0 == observationClass)
    {
        observationClass = [GSObservation class];
    }
    obs = NSAllocateObject(observationClass, 0, _zone);
    
#else
    
    /* Generally, observations are cached and we create a 'new' observation
     * by retrieving from the cache or by allocating a block of observations
     * in one go.  This works nicely to both hide observations from the
     * garbage collector (when using gcc for GC) and to provide high
     * performance for situations where apps add/remove lots of observers
     * very frequently (poor design, but something which happens in the
     * real world unfortunately).
     */
    if (t->freeList == 0)
    {
        Observation *block;
        
        if (t->chunkIndex == CHUNKSIZE)
        {
            unsigned    size;
            
            t->numChunks++;
            
            size = t->numChunks * sizeof(Observation*);
            t->chunks = (Observation**)NSReallocateCollectable(
                                                               t->chunks, size, NSScannedOption);
            
            size = CHUNKSIZE * sizeof(Observation);
            t->chunks[t->numChunks - 1]
            = (Observation*)NSAllocateCollectable(size, 0);
            t->chunkIndex = 0;
        }
        block = t->chunks[t->numChunks - 1];
        t->freeList = &block[t->chunkIndex];
        t->chunkIndex++;
        t->freeList->link = 0;
    }
    obs = t->freeList;
    t->freeList = (Observation*)obs->link;
    obs->link = (void*)t;
    obs->retained = 0;
    obs->next = 0;
#endif
    
    obs->selector = s;
    obs->method = m;
#if GS_WITH_GC
    GSAssignZeroingWeakPointer((void**)&obs->observer, (void*)o);
#else
    obs->observer = o;
#endif
    
    return obs;
}

简化一下:

  • 创建Observation实例;会判断是否支持GC,来创建GSObservation对象或者Observation结构体
  • 将name、selector、observer、imp赋值给实例

GSObservationObservation结构体的定义如下:

/*
 * Observation structure - One of these objects is created for
 * each -addObserver... request.  It holds the requested selector,
 * name and object.  Each struct is placed in one LinkedList,
 * as keyed by the NAME/OBJECT parameters.
 * If 'next' is 0 then the observation is unused (ie it has been
 * removed from, or not yet added to  any list).  The end of a
 * list is marked by 'next' being set to 'ENDOBS'.
 *
 * This is normally a structure which handles memory management using a fast
 * reference count mechanism, but when built with clang for GC, a structure
 * can't hold a zeroing weak pointer to an observer so it's implemented as a
 * trivial class instead ... and gets managed by the garbage collector.
 */
#ifdef __OBJC_GC__

@interface  GSObservation : NSObject
{
  @public
  __weak id observer;   /* Object to receive message.   */
  SEL       selector;   /* Method selector.     */
  IMP       method;     /* Method implementation.   */
  struct Obs    *next;      /* Next item in linked list.    */
  struct NCTbl  *link;      /* Pointer back to chunk table  */
}
@end
@implementation GSObservation
@end
#define Observation GSObservation

#else

typedef struct  Obs {
  id        observer;   /* Object to receive message.   */
  SEL       selector;   /* Method selector.     */
  IMP       method;     /* Method implementation.   */
  struct Obs    *next;      /* Next item in linked list.    */
  int       retained;   /* Retain count for structure.  */
  struct NCTbl  *link;      /* Pointer back to chunk table  */
} Observation;

#endif

可以看到GC的情况下,内部有一个weak修饰的observer属性,弱引用观察者对象

GSAssignZeroingWeakPointer((void**)&obs->observer, (void*)o);

BOOL
GSAssignZeroingWeakPointer(void **destination, void *source)
{
  objc_assign_weak(source, (id*)destination);
  return YES;
}

id objc_assign_weak(id value, id *location) 
    { return (*location = value); }

在非GC的情况下,直接将结构体obs的observer赋值为观察者,关键代码如下

obs->observer = o;

这种情况下,结构体引用观察者,但是不会造成retainCount+1;
由于不知道结构体的内存管理方式,写了测试代码验证在手动管理内存的情况下结构体对对象的引用,引用计数的变化,测试代码如下:

#import "TestCode.h"

typedef struct  Obs {
    id        observer;   /* Object to receive message.   */
    int       retained;
} Observation;

@implementation TestCode

- (instancetype)init {
    self = [super init];
    if (self) {
        [self test];
    }
    
    return self;
}

- (void)test {
    NSLog(@"%ld", (long)CFGetRetainCount((__bridge CFTypeRef)(self)));
    Observation *obs = malloc(sizeof(Observation));
//    obs->retained = 0;
    obs->observer = self;
    NSLog(@"%ld", (long)CFGetRetainCount((__bridge CFTypeRef)(self)));
    /*
     2019-09-19 09:57:41.229922+0800 RuntimeLearning[99635:3579888] 1
     2019-09-19 09:57:44.509753+0800 RuntimeLearning[99635:3579888] 1
     */
}

@end

我们发现在GC或者非GC的情况下,addObserver的时候,内部对观察者的引用,都不造成观察者引用计数的增加;所以这也是我们没有removeObserver的时候,观察者的dealloc也会执行

removeObserver

remove主要是按照observer、name、object入参,删除mapTable中对应的observer

- (void) removeObserver: (id)observer
                   name: (NSString*)name
                 object: (id)object
{
    if (name == nil && object == nil && observer == nil)
        return;
    
    /*
     *  NB. The removal algorithm depends on an implementation characteristic
     *  of our map tables - while enumerating a table, it is safe to remove
     *  the entry returned by the enumerator.
     */
    
    lockNCTable(TABLE);
    
    if (object != nil)
    {
        object = CHEATGC(object);
    }
    
    if (name == nil && object == nil)
    {
        WILDCARD = listPurge(WILDCARD, observer);
    }
    
    if (name == nil)
    {
        GSIMapEnumerator_t  e0;
        GSIMapNode      n0;
        
        /*
         * First try removing all named items set for this object.
         */
        e0 = GSIMapEnumeratorForMap(NAMED);
        n0 = GSIMapEnumeratorNextNode(&e0);
        while (n0 != 0)
        {
            GSIMapTable     m = (GSIMapTable)n0->value.ptr;
            NSString        *thisName = (NSString*)n0->key.obj;
            
            n0 = GSIMapEnumeratorNextNode(&e0);
            if (object == nil)
            {
                GSIMapEnumerator_t  e1 = GSIMapEnumeratorForMap(m);
                GSIMapNode      n1 = GSIMapEnumeratorNextNode(&e1);
                
                /*
                 * Nil object and nil name, so we step through all the maps
                 * keyed under the current name and remove all the objects
                 * that match the observer.
                 */
                while (n1 != 0)
                {
                    GSIMapNode  next = GSIMapEnumeratorNextNode(&e1);
                    
                    purgeMapNode(m, n1, observer);
                    n1 = next;
                }
            }
            else
            {
                GSIMapNode  n1;
                
                /*
                 * Nil name, but non-nil object - we locate the map for the
                 * specified object, and remove all the items that match
                 * the observer.
                 */
                n1 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
                if (n1 != 0)
                {
                    purgeMapNode(m, n1, observer);
                }
            }
            /*
             * If we removed all the observations keyed under this name, we
             * must remove the map table too.
             */
            if (m->nodeCount == 0)
            {
                mapFree(TABLE, m);
                GSIMapRemoveKey(NAMED, (GSIMapKey)(id)thisName);
            }
        }
        
        /*
         * Now remove unnamed items
         */
        if (object == nil)
        {
            e0 = GSIMapEnumeratorForMap(NAMELESS);
            n0 = GSIMapEnumeratorNextNode(&e0);
            while (n0 != 0)
            {
                GSIMapNode  next = GSIMapEnumeratorNextNode(&e0);
                
                purgeMapNode(NAMELESS, n0, observer);
                n0 = next;
            }
        }
        else
        {
            n0 = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
            if (n0 != 0)
            {
                purgeMapNode(NAMELESS, n0, observer);
            }
        }
    }
    else
    {
        GSIMapTable     m;
        GSIMapEnumerator_t  e0;
        GSIMapNode      n0;
        
        /*
         * Locate the map table for this name.
         */
        n0 = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
        if (n0 == 0)
        {
            unlockNCTable(TABLE);
            return;     /* Nothing to do.   */
        }
        m = (GSIMapTable)n0->value.ptr;
        
        if (object == nil)
        {
            e0 = GSIMapEnumeratorForMap(m);
            n0 = GSIMapEnumeratorNextNode(&e0);
            
            while (n0 != 0)
            {
                GSIMapNode  next = GSIMapEnumeratorNextNode(&e0);
                
                purgeMapNode(m, n0, observer);
                n0 = next;
            }
        }
        else
        {
            n0 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
            if (n0 != 0)
            {
                purgeMapNode(m, n0, observer);
            }
        }
        if (m->nodeCount == 0)
        {
            mapFree(TABLE, m);
            GSIMapRemoveKey(NAMED, (GSIMapKey)((id)name));
        }
    }
    unlockNCTable(TABLE);
}

大概的流程就是如下图所示:


NSNotificationCenter源码学习_第2张图片
图片.png

我们调用removeObserver才会将观察者从通知管理中心的mapTable中移除,不移除的话,则Observation还在,当有符合的通知出发,就会发送给Observation,如果observer已经释放了,那么就访问了未知的内容,导致异常;当然iOS9之后已经不需要担心这个问题,系统已经帮我们处理了

postNotification

- (void) postNotificationName: (NSString*)name
                       object: (id)object
                     userInfo: (NSDictionary*)info
{
    GSNotification  *notification;
    
    notification = (id)NSAllocateObject(concrete, 0, NSDefaultMallocZone());
    notification->_name = [name copyWithZone: [self zone]];
    notification->_object = [object retain];
    notification->_info = [info retain];
    [self _postAndRelease: notification];
}
/**
 * Private method to perform the actual posting of a notification.
 * Release the notification before returning, or before we raise
 * any exception ... to avoid leaks.
 */
- (void) _postAndRelease: (NSNotification*)notification
{
    Observation *o;
    unsigned    count;
    NSString    *name = [notification name];
    id      object;
    GSIMapNode  n;
    GSIMapTable m;
    GSIArrayItem    i[64];
    GSIArray_t  b;
    GSIArray    a = &b;
#if GS_WITH_GC
    NSGarbageCollector  *collector = [NSGarbageCollector defaultCollector];
#endif
    
    if (name == nil)
    {
        RELEASE(notification);
        [NSException raise: NSInvalidArgumentException
                    format: @"Tried to post a notification with no name."];
    }
    object = [notification object];
    if (object != nil)
    {
        object = CHEATGC(object);
    }
    
    /*
     * Lock the table of observations while we traverse it.
     *
     * The table of observations contains weak pointers which are zeroed when
     * the observers get garbage collected.  So to avoid consistency problems
     * we disable gc while we copy all the observations we are interested in.
     * We use scanned memory in the array in the case where there are more
     * than the 64 observers we allowed room for on the stack.
     */
#if GS_WITH_GC
    GSIArrayInitWithZoneAndStaticCapacity(a, (NSZone*)1, 64, i);
    [collector disable];
#else
    GSIArrayInitWithZoneAndStaticCapacity(a, _zone, 64, i);
#endif
    lockNCTable(TABLE);
    
    /*
     * Find all the observers that specified neither NAME nor OBJECT.
     */
    for (o = WILDCARD = purgeCollected(WILDCARD); o != ENDOBS; o = o->next)
    {
        GSIArrayAddItem(a, (GSIArrayItem)o);
    }
    
    /*
     * Find the observers that specified OBJECT, but didn't specify NAME.
     */
    if (object)
    {
        n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
        if (n != 0)
        {
            o = purgeCollectedFromMapNode(NAMELESS, n);
            while (o != ENDOBS)
            {
                GSIArrayAddItem(a, (GSIArrayItem)o);
                o = o->next;
            }
        }
    }
    
    /*
     * Find the observers of NAME, except those observers with a non-nil OBJECT
     * that doesn't match the notification's OBJECT).
     */
    if (name)
    {
        n = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
        if (n)
        {
            m = (GSIMapTable)n->value.ptr;
        }
        else
        {
            m = 0;
        }
        if (m != 0)
        {
            /*
             * First, observers with a matching object.
             */
            n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
            if (n != 0)
            {
                o = purgeCollectedFromMapNode(m, n);
                while (o != ENDOBS)
                {
                    GSIArrayAddItem(a, (GSIArrayItem)o);
                    o = o->next;
                }
            }
            
            if (object != nil)
            {
                /*
                 * Now observers with a nil object.
                 */
                n = GSIMapNodeForSimpleKey(m, (GSIMapKey)nil);
                if (n != 0)
                {
                    o = purgeCollectedFromMapNode(m, n);
                    while (o != ENDOBS)
                    {
                        GSIArrayAddItem(a, (GSIArrayItem)o);
                        o = o->next;
                    }
                }
            }
        }
    }
    
    /*
     * Finished with the table ... we can unlock it and re-enable garbage
     * collection, safe in the knowledge that the observers we will be
     * notifying won't get collected prematurely.
     */
    unlockNCTable(TABLE);
#if GS_WITH_GC
    [collector enable];
#endif
    
    /*
     * Now send all the notifications.
     */
    count = GSIArrayCount(a);
    while (count-- > 0)
    {
        o = GSIArrayItemAtIndex(a, count).ext;
        if (o->next != 0)
        {
            NS_DURING
            {
                (*o->method)(o->observer, o->selector, notification);
            }
            NS_HANDLER
            {
                NSLog(@"Problem posting notification: %@", localException);
            }
            NS_ENDHANDLER
        }
    }
    lockNCTable(TABLE);
    GSIArrayEmpty(a);
    unlockNCTable(TABLE);
    
    RELEASE(notification);
}

发送通知的大致流程:

  1. 收集需要发送通知的observations,添加到数组中
  • 收集没有设置name和object的observation
  • 收集observation的object跟通知的object匹配,但是没有设置name的observation
  • 收集observation的name跟通知的name匹配的,包含没有设置object或者object匹配的observation【先添加object匹配的,再添加object为nil的observation】
  1. 遍历发送通知【直接调用imp去执行函数调用】
  • (*o->method)(o->observer, o->selector, notification);
  1. 释放notification对象

至此已经解答了前面的疑问~
那么问题来了:为什么通知中心内部是弱引用,而runloop中添加observer却是强持有了?你知道吗

你可能感兴趣的:(NSNotificationCenter源码学习)