iOS進階之NSNotification的實現(xiàn)原理

一、NSNotification使用

1、向觀察者中心添加觀察者:

  • 方式一:觀察者接收到通知后執(zhí)行任務(wù)的代碼在發(fā)送通知的線程中執(zhí)行
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;

  • 方式二:觀察者接受到通知后執(zhí)行任務(wù)的代碼在指定的操作隊列中執(zhí)行
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block

2、通知中心向觀察者發(fā)送消息

- (void)postNotification:(NSNotification *)notification;

- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;

- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

3、移除觀察者

- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;

二、實現(xiàn)原理

1、首先了解Observation、NCTable這個結(jié)構(gòu)體內(nèi)部結(jié)構(gòu)

當(dāng)你調(diào)用addObserver:selector:name:object:會創(chuàng)建一個Observation,Observation的結(jié)構(gòu)如下代碼:

typedef struct  Obs {
  id observer;  //接受消息的對象   
  SEL selector; //執(zhí)行的方法
  struct Obs    *next;  //下一Obs節(jié)點指針
  int   retained;   //引用計數(shù)
  struct NCTbl *link; //執(zhí)向chunk table指針
} Observation;

對于Observation持有observer:

  • 在iOS9以前:

    • 持有的是一個__unsafe_unretain指針對象,當(dāng)對象釋放時,會訪問已經(jīng)釋放的對象,造成BAD_ACCESS。
    • 在iOS9之后:持有的是weak類型指針,當(dāng)observer釋放時observer會置nil,nil對象performSelector不再會崩潰。
  • name和Observation是映射關(guān)系。

    • observer和sel包含在Observation結(jié)構(gòu)體中。

Observation對象存在哪?

NSNotification維護了全局對象表NCTable結(jié)構(gòu),結(jié)構(gòu)體里包含GSIMapTable表的結(jié)構(gòu),用于存儲Observation。代碼如下:

#define CHUNKSIZE   128
#define CACHESIZE   16
typedef struct NCTbl {
  Observation       *wildcard;  /* Get ALL messages.        */
  GSIMapTable       nameless;   /* Get messages for any name.   */
  GSIMapTable       named;      /* Getting named messages only. */
  unsigned      lockCount;  /* Count recursive operations.  */
  NSRecursiveLock   *_lock;     /* Lock out other threads.  */
  Observation       *freeList;
  Observation       **chunks;
  unsigned      numChunks;
  GSIMapTable       cache[CACHESIZE];
  unsigned short    chunkIndex;
  unsigned short    cacheIndex;
} NCTable;

數(shù)據(jù)結(jié)構(gòu)重要的參數(shù):

  • wildcard:保存既沒有通知名稱又沒有傳入object的通知單鏈表;
  • nameless:存儲沒有傳入名字的通知名稱的hash表。
  • named:存儲傳入了名字的通知的hash表。
  • cache:用于快速緩存.

這里值得注意nameless和named的結(jié)構(gòu),雖然都是hash表,存儲的東西還有點區(qū)別:

  • nameless表中的GSIMapTable的結(jié)構(gòu)如下
key value
object Observation
object Observation
object Observation

沒有傳入名字的nameless表,key就是object參數(shù),vaule為Observation結(jié)構(gòu)體

  • 在named表中GSIMapTable結(jié)構(gòu)如下:
key value
name maptable
name maptable
name maptable
  • maptable也是一個hash表,結(jié)構(gòu)如下:
key value
object Observation
object Observation
object Observation

傳入名字的通知是存放在叫named的hash表
kay為name,value還是maptable也是一個hash表
maptable表的key為object參數(shù),value為Observation參數(shù)

2、addObserver:selector:name:object: 方法內(nèi)部實現(xiàn)原理

- (void) addObserver: (id)observer
            selector: (SEL)selector
                name: (NSString*)name
              object: (id)object
{
    Observation *list;
    Observation *o;
    GSIMapTable m;
    GSIMapNode  n;

    //入?yún)z查異常處理
    ...
        //table加鎖保持數(shù)據(jù)一致性,同一個線程按順序執(zhí)行,是同步的
    lockNCTable(TABLE);
        //創(chuàng)建Observation對象包裝相應(yīng)的調(diào)用函數(shù)
    o = obsNew(TABLE, selector, observer);
        //處理存在通知名稱的情況
    if (name)
    {
        //table表中獲取相應(yīng)name的節(jié)點
        n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
        if (n == 0)
        {
           //未找到相應(yīng)的節(jié)點,則創(chuàng)建內(nèi)部GSIMapTable表,以name作為key添加到talbe中
          m = mapNew(TABLE);
          name = [name copyWithZone: NSDefaultMallocZone()];
          GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
          GS_CONSUMED(name)
        }
        else
        {
            //找到則直接獲取相應(yīng)的內(nèi)部table
            m = (GSIMapTable)n->value.ptr;
        }

        //內(nèi)部table表中獲取相應(yīng)object對象作為key的節(jié)點
        n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
        if (n == 0)
        {
            //不存在此節(jié)點,則直接添加observer對象到table中
            o->next = ENDOBS;//單鏈表observer末尾指向ENDOBS
            GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);
        }
        else
        {
            //存在此節(jié)點,則獲取并將obsever添加到單鏈表observer中
            list = (Observation*)n->value.ptr;
            o->next = list->next;
            list->next = o;
        }
    }
    //只有觀察者對象情況
    else if (object)
    {
        //獲取對應(yīng)object的table
        n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
        if (n == 0)
        {
            //未找到對應(yīng)object key的節(jié)點,則直接添加observergnustep-base-1.25.0
            o->next = ENDOBS;
            GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);
        }
        else
        {
            //找到相應(yīng)的節(jié)點則直接添加到鏈表中
            list = (Observation*)n->value.ptr;
            o->next = list->next;
            list->next = o;
        }
    }
    //處理即沒有通知名稱也沒有觀察者對象的情況
    else
    {
        //添加到單鏈表中
        o->next = WILDCARD;
        WILDCARD = o;
    }
        //解鎖
    unlockNCTable(TABLE);
}

添加通知的基本邏輯:

  1. 根據(jù)傳入的selector和observer創(chuàng)建Observation,并存入GSIMaptable中,如果已存在,則是從cache中取。

  2. 如果name存在:

    • 則向named表中插入元素,key為name,value為GSIMaptable。
    • GSIMaptable里面key為object,value為Observation,結(jié)束
  3. 如果name不存在:

    • 則向nameless表中插入元素,key為object,value為Observation,結(jié)束
  4. 如果name和object都不存在,則把這個Observation添加WILDCARD鏈表中

三、addObserverForName:object:queueusingBlock:實現(xiàn)原理

//對于block形式,里面創(chuàng)建了GSNotificationObserver對象,然后在調(diào)用addObserver: selector: name: object:
- (id) addObserverForName: (NSString *)name 
                   object: (id)object 
                    queue: (NSOperationQueue *)queue 
               usingBlock: (GSNotificationBlock)block
{
    GSNotificationObserver *observer = 
        [[GSNotificationObserver alloc] initWithQueue: queue block: block];

    [self addObserver: observer 
             selector: @selector(didReceiveNotification:) 
                 name: name 
               object: object];

    return observer;
}

/*
1.初始化該隊列會創(chuàng)建Block_copy 拷貝block
2.并確定通知操作隊列
*/
- (id) initWithQueue: (NSOperationQueue *)queue 
               block: (GSNotificationBlock)block
{
    self = [super init];
    if (self == nil)
        return nil;

    ASSIGN(_queue, queue);
    _block = Block_copy(block);
    return self;
}

/*
1.通知的接受處理函數(shù)didReceiveNotification,
2.如果queue不為空,通過addOperation來實現(xiàn)指定操作隊列處理
3.如果queue不為空,直接在當(dāng)前線程執(zhí)行block。
*/
- (void) didReceiveNotification: (NSNotification *)notif
{
    if (_queue != nil)
    {
        GSNotificationBlockOperation *op = [[GSNotificationBlockOperation alloc] 
            initWithNotification: notif block: _block];

        [_queue addOperation: op];
    }
    else
    {
        CALL_BLOCK(_block, notif);
    }
}

4、發(fā)送通知的實現(xiàn) postNotificationName: name: object:

 - (void) _postAndRelease: (NSNotification*)notification
{
    1.入?yún)z查校驗
    2.創(chuàng)建存儲所有匹配通知的數(shù)組GSIArray
    3.加鎖table避免數(shù)據(jù)一致性問題
    4.查找既不監(jiān)聽name也不監(jiān)聽object所有的wildcard類型的Observation,加入數(shù)組GSIArray中
    5.查找NAMELESS表中指定對應(yīng)觀察者對象object的Observation并添加到數(shù)組中
    6.查找NAMED表中相應(yīng)的Observation并添加到數(shù)組中
        1. 首先查找name與object的一致的Observation加入數(shù)組中
        2. 當(dāng)object為nil的Observation加入數(shù)組中
    //解鎖table
    //遍歷整個數(shù)組并依次調(diào)用performSelector:withObject處理通知消息發(fā)送
    //解鎖table并釋放資源
}

二、NSNotification相關(guān)問題

1、對于addObserver方法,為什么需要object參數(shù)?

  1. addObserver當(dāng)你不傳入name也可以,傳入object,當(dāng)postNotification方法同樣發(fā)出這個object時,就會觸發(fā)通知方法。

因為當(dāng)name不存在的時候,會繼續(xù)判斷object,則向nameless的maptable表中插入元素,key為object,value為Observation

2、都傳入null對象會怎么樣

你可能也注意到了,addObserver方法name和object都可以為空,這表示將會把observer賦值為 wildcard,他將會監(jiān)聽所有的通知。

3、通知的發(fā)送時同步的,還是異步的。

同步異步這個問題,由于TABLE資源的問題,同一個線程會按順序遍歷數(shù)組執(zhí)行,自然是同步的。

4、NSNotificationCenter接受消息和發(fā)送消息是在一個線程里嗎?如何異步發(fā)送消息

由于是使用的performSelector方法,沒有進行轉(zhuǎn)線程,默認是postNotification方法的線程。

[o->observer performSelector: o->selector 
withObject: notification];

對于異步發(fā)送消息,可以使用NSNotificationQueue,queue顧明意思,我們是需要將NSNotification放入queue中執(zhí)行的。

NSNotificationQueue發(fā)送消息的三種模式:

typedef NS_ENUM(NSUInteger, NSPostingStyle) {
    NSPostWhenIdle = 1, // 當(dāng)runloop處于空閑狀態(tài)時post
    NSPostASAP = 2, // 當(dāng)當(dāng)前runloop完成之后立即post
    NSPostNow = 3  // 立即post
};

NSNotification *notification = [NSNotification notificationWithName:@"111" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification: notification postingStyle:NSPostASAP];
  • NSPostingStyle為NSPostNow 模式是同步發(fā)送,
  • NSPostWhenIdle或者NSPostASAP是異步發(fā)送

5、NSNotificationQueue和runloop的關(guān)系?

NSNotificationQueue 是依賴runloop才能成功觸發(fā)通知,如果去掉runloop的方法,你會發(fā)現(xiàn)無法觸發(fā)通知。

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    //子線程的runloop需要自己主動開啟     
   NSNotification *notification = [NSNotification notificationWithName:@"TEST" object:nil];
        [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:@[NSDefaultRunLoopMode]];
        // run runloop
        [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSRunLoopCommonModes];
        CFRunLoopRun();
        NSLog(@"3");
    });

NSNotification *notification = [NSNotification notificationWithName:@"111" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification: notification postingStyle:NSPostASAP];

NSNotificationQueue將通知添加到隊列中時,其中postringStyle參數(shù)就是定義通知調(diào)用和runloop狀態(tài)之間關(guān)系。

6、如何保證通知接收的線程在主線程?

  1. 保證主線程發(fā)送消息或者接受消息方法里切換到主線程

  2. 接收到通知后跳轉(zhuǎn)到主線程,蘋果建議使用NSMachPort進行消息轉(zhuǎn)發(fā)到主線程。

實現(xiàn)代碼如下:

machPort轉(zhuǎn)發(fā)到主線程_1.png
machPort轉(zhuǎn)發(fā)到主線程_2.png
machPort轉(zhuǎn)發(fā)到主線程_3.png
machPort轉(zhuǎn)發(fā)到主線程_4.png
machPort轉(zhuǎn)發(fā)到主線程_5.png
  1. 使用block接口addObserverForName:object:queue:usingBlock:指定主線程

7、頁面銷毀時不移除通知會崩潰嗎?

在iOS9之前會,iOS9之后不會

對于Observation持有observer

在iOS9之前:不是一個類似OC中的weak類型,持有的相當(dāng)與一個__unsafe_unretain指針對象,當(dāng)對象釋放時,會訪問已經(jīng)釋放的對象,造成BAD_ACCESS。
在iOS9之后:持有的是weak類型指針,對nil對象performSelector不再會崩潰

8、多次添加同一個通知會是什么結(jié)果?多次移除通知呢?

  1. 由于源碼中并不會進行重復(fù)過濾,所以添加同一個通知,等于就是添加了2次,回調(diào)也會觸發(fā)兩次。

  2. 關(guān)于多次移除,并沒有問題,因為會去map中查找,找到才會刪除。當(dāng)name和object都為nil時,會移除所有關(guān)于該observer的WILDCARD

9、下面的方式能接收到通知嗎?為什么

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:@1];

[NSNotificationCenter.defaultCenter postNotificationName:@"TestNotification" object:nil];

根據(jù)postNotification的實現(xiàn):

  • 會找到key為TestNotification的maptable,
  • 再從中選擇key為nil的observation,
  • 所以是找不到以@1為key的observation的
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容