Notification的命名方式及定義方法
[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification
Apple范例:
NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification
在發(fā)送通知的實現(xiàn)文件中,按如下方式定義:
NSNotificationName const 通知名 = @"text notification";
在需要接收改通知的類文件的頂部按如下方式聲明該通知變量:
UIKIT_EXTERN NSNotificationName const 通知名;
NSNotificationName==NSString *
UIKIT_EXTERN==extern
具體詳情可以參考下面大神的鏈接。
參考來源:http://www.itdecent.cn/p/761f302c0bd5
NSNotification用法
添加觀察者的兩種方式:
方式一:
- (void)addObserver:(id)notificationObserver
selector:(SEL)notificationSelector
name:(NSString *)notificationName
object:(id)notificationSender
- notificationObserver不能為nil。
- notificationSelector回調方法有且只有一個參數(shù)(NSNotification對象)。
- 如果notificationName為nil,則會接收所有的通知(如果notificationSender不為空,則接收所有來自于notificationSender的所有通知)。
- 如果notificationSender為nil,則會接收所有notificationName定義的通知;否則,接收由notificationSender發(fā)送的通知。
- 監(jiān)聽同一條通知的多個觀察者,在通知到達時,它們執(zhí)行回調的順序是不確定的,所以我們不能去假設操作的執(zhí)行會按照添加觀察者的順序來執(zhí)行。
方式二:
- (id<NSObject>)addObserverForName:(NSString *)name
object:(id)obj
queue:(NSOperationQueue *)queue
usingBlock:(void (^)(NSNotification *note))block
name和obj為nil時的情形與前面一個方法是相同的。
如果queue為nil,則消息是默認在post線程中同步處理,即通知的post與轉發(fā)是在同一線程中;但如果我們指定了操作隊列,不管通知是在哪個線程中post的,都會在Operation Queue所屬的線程中進行轉發(fā)。
block塊會被通知中心拷貝一份(執(zhí)行copy操作),以在堆中維護一個block對象,直到觀察者被從通知中心中移除。所以,應該特別注意在block中使用外部對象,避免出現(xiàn)對象的循環(huán)引用。
如果一個給定的通知觸發(fā)了多個觀察者的block操作,則這些操作會在各自的Operation Queue中被并發(fā)執(zhí)行。所以我們不能去假設操作的執(zhí)行會按照添加觀察者的順序來執(zhí)行。
該方法會返回一個表示觀察者的對象,記得在不用時釋放這個對象。關于注冊監(jiān)聽者,還有一個需要注意的問題是,每次調用addObserver時,都會在通知中心重新注冊一次,即使是同一對象監(jiān)聽同一個消息,而不是去覆蓋原來的監(jiān)聽。這樣,當通知中心轉發(fā)某一消息時,如果同一對象多次注冊了這個通知的觀察者,則會收到多個通知。
移除觀察者的方式
- (void)removeObserver:(id)notificationObserver
- (void)removeObserver:(id)notificationObserver
name:(NSString *)notificationName
object:(id)notificationSender
由于注冊觀察者時(不管是哪個方法),通知中心會維護一個觀察者的弱引用,所以在釋放對象時,要確保移除對象所有監(jiān)聽的通知。否則,可能會導致程序崩潰或一些莫名其妙的問題。
對于第二個方法,如果notificationName為nil,則會移除所有匹配notificationObserver和notificationSender的通知,同理notificationSender也是一樣的。而如果notificationName和notificationSender都為nil,則其效果就與第一個方法是一樣的了。
最有趣的應該是這兩個方法的使用時機。
–removeObserver:適合于在類的dealloc方法中調用,這樣可以確保將對象從通知中心中清除;而在viewWillDisappear:這樣的方法中,則適合于使用-removeObserver:name:object:方法,以避免不知情的情況下移除了不應該移除的通知觀察者。例如,假設我們的ViewController繼承自一個類庫的某個ViewController類(假設為SKViewController吧),可能SKViewController自身也監(jiān)聽了某些通知以執(zhí)行特定的操作,但我們使用時并不知道。如果直接在viewWillDisappear:中調用–removeObserver:,則也會把父類監(jiān)聽的通知也給移除。
post消息
- postNotification:
– postNotificationName:object:
– postNotificationName:object:userInfo:
- 每次post一個通知時,通知中心都會去遍歷一下它的分發(fā)表,然后將通知轉發(fā)給相應的觀察者。
- 通知的發(fā)送與處理是同步的,在某個地方post一個消息時,會等到所有觀察者對象執(zhí)行完處理操作后,才回到post的地方,繼續(xù)執(zhí)行后面的代碼。
通知中心是如何維護觀察者對象的
上面這個問題在《斯坦福大學公開課:iOS 7應用開發(fā)》的第5集的第57分50秒中得到了解答:確實使用的是unsafe_unretained,老師的解釋是,之所以使用unsafe_unretained,而不使用weak,是為了兼容老版本的系統(tǒng)。
iOS8及以前,NSNotificationCenter持有的是觀察者的unsafe_unretained指針(可能是為了兼容老版本),這樣,在觀察者回收的時候未removeOberser,而后再進行post操作,則會向一段被回收的區(qū)域發(fā)送消息,所以出現(xiàn)野指針crash。而iOS9以后,unsafe_unretained改成了weak指針,即使dealloc的時候未removeOberser,再進行post操作,則會向nil發(fā)送消息,所以沒有任何問題。
Notification Queues和異步通知
異步通知原理
創(chuàng)建一個NSNotificationQueue隊列(first in-first out),將定義的NSNotification放入其中,并為其指定三種狀態(tài)之一:
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1, // 當runloop處于空閑狀態(tài)時post
NSPostASAP = 2, // 當當前runloop完成之后立即post
NSPostNow = 3 // 立即post,同步(為什么需要這種type,且看三.3)
};
異步通知的使用
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
A *a = [A new];
[a test];
self.a = a;
NSNotification *noti = [NSNotification notificationWithName:@"111" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostASAP];
//[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostWhenIdle];
//[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostNow];
NSLog(@"測試同步還是異步");
return YES;
}
// 輸出
2017-02-26 19:56:32.805 notification[19406:12719309] 測試同步還是異步
2017-02-26 19:56:32.816 notification[19406:12719309] selector 1
2017-02-26 19:56:32.816 notification[19406:12719309] block 2
Notification Queues的合成作用
NSNotificationQueue除了有異步通知的能力之外,也能對當前隊列的通知根據(jù)NSNotificationCoalescing類型進行合成(即將幾個合成一個)。
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
NSNotificationNoCoalescing = 0, // 不合成
NSNotificationCoalescingOnName = 1, // 根據(jù)NSNotification的name字段進行合成
NSNotificationCoalescingOnSender = 2 // 根據(jù)NSNotification的object字段進行合成
};
指定Thread處理通知
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
A *a = [A new];
[a test];
self.a = a;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(queue, ^{
NSLog(@"current thread %@", [NSThread currentThread]);
[[NSNotificationCenter defaultCenter] postNotificationName:@"111" object:nil];
});
return YES;
}
可知,a中的observer的selector將會在DISPATCH_QUEUE_PRIORITY_BACKGROUND中執(zhí)行,若該selector執(zhí)行的是刷新UI的操作,那么這種方式顯然是錯誤的。這里,我們需要保證selector永遠在mainThread執(zhí)行。所以,有以下方式,指定observer的回調方法的執(zhí)行線程:
// 代碼
@interface A : NSObject
- (void)test;
@end
@implementation A
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)test {
[[NSNotificationCenter defaultCenter] addObserverForName:@"111" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"current thread %@ 刷新UI", [NSThread currentThread]);
// 刷新UI ...
}];
}
@end
// 輸出
current thread <NSThread: 0x7bf29110>{number = 3, name = (null)}
2017-02-27 11:53:46.531 notification[29510:12833116] current thread <NSThread: 0x7be1d6f0>{number = 1, name = main} 刷新UI
對象之間的通信方式主要有以下幾種:
- 直接方法調用
- Target-Action
- Delegate
- 回調(block)
- KVO
- 通知