一、NSNotification使用
1、向觀察者中心添加觀察者:
- 方式一:觀察者接收到通知后執行任務的代碼在發送通知的線程中執行
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
- 方式二:觀察者接受到通知后執行任務的代碼在指定的操作隊列中執行
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block
2、通知中心向觀察者發送消息
- (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;
二、實現原理
1、首先了解Observation、NCTable這個結構體內部結構
當你調用addObserver:selector:name:object:會創建一個Observation,Observation的結構如下代碼:
typedef struct Obs {
id observer; //接受消息的對象
SEL selector; //執行的方法
struct Obs *next; //下一Obs節點指針
int retained; //引用計數
struct NCTbl *link; //執向chunk table指針
} Observation;
對于Observation持有observer:
-
在iOS9以前:
- 持有的是一個__unsafe_unretain指針對象,當對象釋放時,會訪問已經釋放的對象,造成BAD_ACCESS。
- 在iOS9之后:持有的是weak類型指針,當observer釋放時observer會置nil,nil對象performSelector不再會崩潰。
-
name和Observation是映射關系。
- observer和sel包含在Observation結構體中。
Observation對象存在哪?
NSNotification維護了全局對象表NCTable結構,結構體里包含GSIMapTable表的結構,用于存儲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;
數據結構重要的參數:
- wildcard:保存既沒有通知名稱又沒有傳入object的通知單鏈表;
- nameless:存儲沒有傳入名字的通知名稱的hash表。
- named:存儲傳入了名字的通知的hash表。
- cache:用于快速緩存.
這里值得注意nameless和named的結構,雖然都是hash表,存儲的東西還有點區別:
- nameless表中的GSIMapTable的結構如下
key | value |
---|---|
object | Observation |
object | Observation |
object | Observation |
沒有傳入名字的nameless表,key就是object參數,vaule為Observation結構體
- 在named表中GSIMapTable結構如下:
key | value |
---|---|
name | maptable |
name | maptable |
name | maptable |
- maptable也是一個hash表,結構如下:
key | value |
---|---|
object | Observation |
object | Observation |
object | Observation |
傳入名字的通知是存放在叫named的hash表
kay為name,value還是maptable也是一個hash表
maptable表的key為object參數,value為Observation參數
2、addObserver:selector:name:object: 方法內部實現原理
- (void) addObserver: (id)observer
selector: (SEL)selector
name: (NSString*)name
object: (id)object
{
Observation *list;
Observation *o;
GSIMapTable m;
GSIMapNode n;
//入參檢查異常處理
...
//table加鎖保持數據一致性,同一個線程按順序執行,是同步的
lockNCTable(TABLE);
//創建Observation對象包裝相應的調用函數
o = obsNew(TABLE, selector, observer);
//處理存在通知名稱的情況
if (name)
{
//table表中獲取相應name的節點
n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
if (n == 0)
{
//未找到相應的節點,則創建內部GSIMapTable表,以name作為key添加到talbe中
m = mapNew(TABLE);
name = [name copyWithZone: NSDefaultMallocZone()];
GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
GS_CONSUMED(name)
}
else
{
//找到則直接獲取相應的內部table
m = (GSIMapTable)n->value.ptr;
}
//內部table表中獲取相應object對象作為key的節點
n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
if (n == 0)
{
//不存在此節點,則直接添加observer對象到table中
o->next = ENDOBS;//單鏈表observer末尾指向ENDOBS
GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);
}
else
{
//存在此節點,則獲取并將obsever添加到單鏈表observer中
list = (Observation*)n->value.ptr;
o->next = list->next;
list->next = o;
}
}
//只有觀察者對象情況
else if (object)
{
//獲取對應object的table
n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
if (n == 0)
{
//未找到對應object key的節點,則直接添加observergnustep-base-1.25.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);
}
添加通知的基本邏輯:
根據傳入的selector和observer創建Observation,并存入GSIMaptable中,如果已存在,則是從cache中取。
-
如果name存在:
- 則向named表中插入元素,key為name,value為GSIMaptable。
- GSIMaptable里面key為object,value為Observation,結束
-
如果name不存在:
- 則向nameless表中插入元素,key為object,value為Observation,結束
如果name和object都不存在,則把這個Observation添加WILDCARD鏈表中
三、addObserverForName:object:queueusingBlock:實現原理
//對于block形式,里面創建了GSNotificationObserver對象,然后在調用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.初始化該隊列會創建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.通知的接受處理函數didReceiveNotification,
2.如果queue不為空,通過addOperation來實現指定操作隊列處理
3.如果queue不為空,直接在當前線程執行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、發送通知的實現 postNotificationName: name: object:
- (void) _postAndRelease: (NSNotification*)notification
{
1.入參檢查校驗
2.創建存儲所有匹配通知的數組GSIArray
3.加鎖table避免數據一致性問題
4.查找既不監聽name也不監聽object所有的wildcard類型的Observation,加入數組GSIArray中
5.查找NAMELESS表中指定對應觀察者對象object的Observation并添加到數組中
6.查找NAMED表中相應的Observation并添加到數組中
1. 首先查找name與object的一致的Observation加入數組中
2. 當object為nil的Observation加入數組中
//解鎖table
//遍歷整個數組并依次調用performSelector:withObject處理通知消息發送
//解鎖table并釋放資源
}
二、NSNotification相關問題
1、對于addObserver方法,為什么需要object參數?
- addObserver當你不傳入name也可以,傳入object,當postNotification方法同樣發出這個object時,就會觸發通知方法。
因為當name不存在的時候,會繼續判斷object,則向nameless的maptable表中插入元素,key為object,value為Observation
2、都傳入null對象會怎么樣
你可能也注意到了,addObserver方法name和object都可以為空,這表示將會把observer賦值為 wildcard,他將會監聽所有的通知。
3、通知的發送時同步的,還是異步的。
同步異步這個問題,由于TABLE資源的問題,同一個線程會按順序遍歷數組執行,自然是同步的。
4、NSNotificationCenter接受消息和發送消息是在一個線程里嗎?如何異步發送消息
由于是使用的performSelector方法,沒有進行轉線程,默認是postNotification方法的線程。
[o->observer performSelector: o->selector
withObject: notification];
對于異步發送消息,可以使用NSNotificationQueue,queue顧明意思,我們是需要將NSNotification放入queue中執行的。
NSNotificationQueue發送消息的三種模式:
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1, // 當runloop處于空閑狀態時post
NSPostASAP = 2, // 當當前runloop完成之后立即post
NSPostNow = 3 // 立即post
};
NSNotification *notification = [NSNotification notificationWithName:@"111" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification: notification postingStyle:NSPostASAP];
- NSPostingStyle為NSPostNow 模式是同步發送,
- NSPostWhenIdle或者NSPostASAP是異步發送
5、NSNotificationQueue和runloop的關系?
NSNotificationQueue 是依賴runloop才能成功觸發通知,如果去掉runloop的方法,你會發現無法觸發通知。
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參數就是定義通知調用和runloop狀態之間關系。
6、如何保證通知接收的線程在主線程?
保證主線程發送消息或者接受消息方法里切換到主線程
接收到通知后跳轉到主線程,蘋果建議使用NSMachPort進行消息轉發到主線程。
實現代碼如下:
- 使用block接口addObserverForName:object:queue:usingBlock:指定主線程
7、頁面銷毀時不移除通知會崩潰嗎?
在iOS9之前會,iOS9之后不會
對于Observation持有observer
在iOS9之前:不是一個類似OC中的weak類型,持有的相當與一個__unsafe_unretain指針對象,當對象釋放時,會訪問已經釋放的對象,造成BAD_ACCESS。
在iOS9之后:持有的是weak類型指針,對nil對象performSelector不再會崩潰
8、多次添加同一個通知會是什么結果?多次移除通知呢?
由于源碼中并不會進行重復過濾,所以添加同一個通知,等于就是添加了2次,回調也會觸發兩次。
關于多次移除,并沒有問題,因為會去map中查找,找到才會刪除。當name和object都為nil時,會移除所有關于該observer的WILDCARD
9、下面的方式能接收到通知嗎?為什么
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:@1];
[NSNotificationCenter.defaultCenter postNotificationName:@"TestNotification" object:nil];
根據postNotification的實現:
- 會找到key為TestNotification的maptable,
- 再從中選擇key為nil的observation,
- 所以是找不到以@1為key的observation的