參考:
南峰子的技術(shù)博客:NSNotificationCenter
天口三水羊:NSNotification,看完你就都懂了
一文全解iOS通知機(jī)制(GNUStep源碼去理解)
監(jiān)聽通知
方法 - (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
參數(shù)解釋:
1、observer 觀察者(不能為nil,通知中心會弱引用,ARC是iOS9之前是unsafe_unretained,iOS9及以后是weak,MRC是assign,所以這也是MRC不移除會crash,ARC不移除不會crash的原因,建議都要嚴(yán)格移除。)
2、aSelector 收到消息后要執(zhí)行的方法
3、aName 消息通知的名字(如果name設(shè)置為nil,則表示接收所有消息)
4、anObject 消息發(fā)送者(表示接收哪個發(fā)送者的通知,如果為nil,接收所有發(fā)送者的通知)
注意:
1、每次調(diào)用addObserver時,都會在通知中心重新注冊一次,即使是同一對象監(jiān)聽同一個消息,而不是去覆蓋原來的監(jiān)聽。這樣,當(dāng)通知中心轉(zhuǎn)發(fā)某一消息時,如果同一對象多次注冊了這個通知的觀察者,則會收到多個通知。
代碼例子:
// 監(jiān)聽通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(aaaa) name:@"AAAA" object:nil];
發(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;
參數(shù)解析
1、aName 消息通知的名字、
2、anObject 消息發(fā)送者、
3、aUserInfo 傳遞的數(shù)據(jù)
代碼例子
//發(fā)送通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"AAAA" object:nil];
移除通知
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject ;
代碼例子
// 移除self的全部通知
[[NSNotificationCenter defaultCenter] removeObserver:self];
// 移除self的AAAA通知
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"AAAA" object:nil];
Block方式的監(jiān)聽消息
方法:- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;
參數(shù)解析:
1、name 消息通知的名字
2、obj 消息發(fā)送者(表示接收哪個發(fā)送者的通知,如果第四個參數(shù)為nil,接收所有發(fā)送者的通知)
3、queue 如果queue為nil,則消息是默認(rèn)在post線程中同一線程同步處理;但如果我們指定了操作隊(duì)列,就在指定的隊(duì)列里面執(zhí)行。
4、block block塊會被通知中心拷貝一份(執(zhí)行copy操作),需要注意的就是避免引起循環(huán)引用的問題,block里面不能使用self,需要使用weakSelf。
5、返回值,observer觀察者的對象,最后需要[[NSNotificationCenter defaultCenter] removeObserver:observer];
注意:
1、block塊會被通知中心拷貝一份(執(zhí)行copy操作),需要注意的就是避免引起循環(huán)引用的問題,block里面不能使用self,需要使用weakSelf。
2、一定要記得[[NSNotificationCenter defaultCenter] removeObserver:observer];
3、如果queue為nil,則消息是默認(rèn)在post線程中同一線程同步處理;但如果指定了操作隊(duì)列,就在指定的隊(duì)列里面執(zhí)行。
代碼例子
//__block __weak id<NSObject> observer 可以在block里面移除
// 只監(jiān)聽一次的使用(消息執(zhí)行完,在block里面移除observer)
__block __weak id<NSObject> observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"AAAA" object:nil queue:NULL usingBlock:^(NSNotification *note) {
NSLog(@"note : %@", note.object);
// 移除通知
[[NSNotificationCenter defaultCenter] removeObserver:observer];
}];
NSNotificationCenter的同步和異步
測試1
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(aaaa) name:@"AAAA" object:nil];
//發(fā)送通知
NSLog(@"發(fā)送通知的線程=====%@",[NSThread currentThread]);
[[NSNotificationCenter defaultCenter] postNotificationName:@"AAAA" object:nil];
}
- (void)aaaa {
NSLog(@"通知執(zhí)行的方法線程=====%@",[NSThread currentThread]);
}
/*運(yùn)行輸出:
發(fā)送通知的線程=====<NSThread: 0x604000072f00>{number = 1, name = main}
通知執(zhí)行的方法線程=====<NSThread: 0x604000072f00>{number = 1, name = main}
*/
測試2
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(aaaa) name:@"AAAA" object:nil];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"發(fā)送通知的線程--post前=====%@",[NSThread currentThread]);
[[NSNotificationCenter defaultCenter] postNotificationName:@"AAAA" object:nil];
NSLog(@"發(fā)送通知的線程--post后=====%@",[NSThread currentThread]);
});
}
- (void)aaaa {
NSLog(@"通知執(zhí)行的方法線程=====%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:5];
}
/*
2017-10-25 17:21:13.847114+0800 SortDemo[18827:46789692] 發(fā)送通知的線程--post前=====<NSThread: 0x60400027bac0>{number = 3, name = (null)}
2017-10-25 17:21:13.847352+0800 SortDemo[18827:46789692] 通知執(zhí)行的方法線程=====<NSThread: 0x60400027bac0>{number = 3, name = (null)}
2017-10-25 17:21:18.850760+0800 SortDemo[18827:46789692] 發(fā)送通知的線程--post后=====<NSThread: 0x60400027bac0>{number = 3, name = (null)}
*/
得知:
1、執(zhí)行監(jiān)聽方法是在發(fā)送通知的當(dāng)前線程
2、發(fā)送通知與執(zhí)行監(jiān)聽方法是同步的(發(fā)送通知會等待全部監(jiān)聽執(zhí)行完)
所以關(guān)于界面的操作用到通知就需要注意:通知發(fā)送是什么線程?執(zhí)行監(jiān)聽方法是不是耗時的任務(wù)?
處理同步問題的辦法:
1、監(jiān)聽方法里面開線程或者異步執(zhí)行
- (void)aaaa {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
});
}
2、NSNotificationQueue的NSPostASAP
NSLog(@"發(fā)送通知的線程--post前=====%@",[NSThread currentThread]);
NSNotification *notification = [NSNotification notificationWithName:@"AAAA"
object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification
postingStyle:NSPostASAP];
NSLog(@"發(fā)送通知的線程--post后=====%@",[NSThread currentThread]);
NSNotificationQueue
NSNotificationQueue給通知機(jī)制提供了2個重要的特性:
1、異步發(fā)送通知
2、通知合并
NSPostingStyle和NSNotificationCoalescing
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1, (當(dāng)runloop處于空閑狀態(tài)時post)
NSPostASAP = 2, (當(dāng)runloop能夠調(diào)用的時候立即post)
NSPostNow = 3 (立即post,同步)
};
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
NSNotificationNoCoalescing = 0, (不合成)
NSNotificationCoalescingOnName = 1, (根據(jù)NSNotification的name字段進(jìn)行合成)
NSNotificationCoalescingOnSender = 2 (根據(jù)NSNotification的object字段進(jìn)行合成)
};
/**
隊(duì)列發(fā)送通知
@param notification 通知對象
@param postingStyle 發(fā)送時機(jī)
@param coalesceMask 合并規(guī)則(可以用|符號連接,指定多個)
@param modes NSRunLoopMode 當(dāng)指定了某種特定runloop mode后,該通知值有在當(dāng)前runloop為指定mode的下,才會被發(fā)出(多線程使用時候,要開啟線程的runloop且響應(yīng)的mode)
*/
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray<NSRunLoopMode> *)modes;
簡單demo
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(aaaa:) name:@"AAAA" object:nil];
NSNotification *noti1 = [NSNotification notificationWithName:@"AAAA" object:@{@"1111":@"1111"}];
NSNotification *noti2 = [NSNotification notificationWithName:@"AAAA" object:@{@"2222":@"2222"}];
NSNotification *noti3 = [NSNotification notificationWithName:@"AAAA" object:@{@"3333":@"3333"}];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti1 postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName forModes:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti2 postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName forModes:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti3 postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName forModes:nil];
// 三個noti,監(jiān)聽方法只執(zhí)行一次,還有NSPostNow使用合并沒有效果
}
- (void)aaaa:(NSNotification *)noti {
NSLog(@"=====%@",noti.object); // 接受到的信息是noti1的 (這個階段第一個enqueueNotification)
}
所以:
1、當(dāng)前runloop狀態(tài)使用合并通知,監(jiān)聽方法只執(zhí)行一次;
2、監(jiān)聽方法接受到信息是(當(dāng)前runloop狀態(tài)第一個enqueueNotification的信息object)
3、由于NSPostNow性質(zhì)可知,不能用于通知合成。
系統(tǒng)的通知Name
// 當(dāng)程序被推送到后臺時
UIKIT_EXTERN NSNotificationName const UIApplicationDidEnterBackgroundNotification NS_AVAILABLE_IOS(4_0);
// 當(dāng)程序從后臺將要重新回到前臺時
UIKIT_EXTERN NSNotificationName const UIApplicationWillEnterForegroundNotification NS_AVAILABLE_IOS(4_0);
// 當(dāng)程序完成載入后通知
UIKIT_EXTERN NSNotificationName const UIApplicationDidFinishLaunchingNotification;
// 應(yīng)用程序轉(zhuǎn)為激活狀態(tài)時
UIKIT_EXTERN NSNotificationName const UIApplicationDidBecomeActiveNotification;
// 用戶按下主屏幕按鈕調(diào)用通知,并未進(jìn)入后臺狀態(tài)
UIKIT_EXTERN NSNotificationName const UIApplicationWillResignActiveNotification;
// 內(nèi)存較低時通知
UIKIT_EXTERN NSNotificationName const UIApplicationDidReceiveMemoryWarningNotification;
// 當(dāng)程序?qū)⒁顺鰰r通知
UIKIT_EXTERN NSNotificationName const UIApplicationWillTerminateNotification;
// 當(dāng)系統(tǒng)時間發(fā)生改變時通知
UIKIT_EXTERN NSNotificationName const UIApplicationSignificantTimeChangeNotification;
// 當(dāng)StatusBar框方向?qū)⒁兓瘯r通知
UIKIT_EXTERN NSNotificationName const UIApplicationWillChangeStatusBarOrientationNotification __TVOS_PROHIBITED; // userInfo contains NSNumber with new orientation
// 當(dāng)StatusBar框方向改變時通知
UIKIT_EXTERN NSNotificationName const UIApplicationDidChangeStatusBarOrientationNotification __TVOS_PROHIBITED; // userInfo contains NSNumber with old orientation
// 當(dāng)StatusBar框Frame將要改變時通知
UIKIT_EXTERN NSNotificationName const UIApplicationWillChangeStatusBarFrameNotification __TVOS_PROHIBITED; // userInfo contains NSValue with new frame
// 當(dāng)StatusBar框Frame改變時通知
UIKIT_EXTERN NSNotificationName const UIApplicationDidChangeStatusBarFrameNotification __TVOS_PROHIBITED; // userInfo contains NSValue with old frame
// 后臺下載狀態(tài)發(fā)生改變時通知(iOS7.0以后可用)
UIKIT_EXTERN NSNotificationName const UIApplicationBackgroundRefreshStatusDidChangeNotification NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED;
// 受保護(hù)的文件當(dāng)前變?yōu)椴豢捎脮r通知
UIKIT_EXTERN NSNotificationName const UIApplicationProtectedDataWillBecomeUnavailable NS_AVAILABLE_IOS(4_0);
// 受保護(hù)的文件當(dāng)前變?yōu)榭捎脮r通知
UIKIT_EXTERN NSNotificationName const UIApplicationProtectedDataDidBecomeAvailable NS_AVAILABLE_IOS(4_0);
// 截屏通知(iOS7.0以后可用)
UIKIT_EXTERN NSNotificationName const UIApplicationUserDidTakeScreenshotNotification NS_AVAILABLE_IOS(7_0);