参考:
南峰子的技术博客:NSNotificationCenter
天口三水羊:NSNotification,看完你就都懂了
监听通知
/**
监听通知
@param observer 观察者(不能为nil,通知中心会弱引用,ARC是weak,MRC是assign,所以这也是MRC不移除会crash,ARC不移除不会crash的原因,建议都要严格移除。)
@param aSelector 收到消息后要执行的方法
@param aName 消息通知的名字(如果name设置为nil,则表示接收所有消息)
@param anObject 消息发送者(表示接收哪个发送者的通知,如果第四个参数为nil,接收所有发送者的通知)
*/
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
// 监听通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(aaaa) name:@"AAAA" object:nil];
注意:
1、每次调用addObserver时,都会在通知中心重新注册一次,即使是同一对象监听同一个消息,而不是去覆盖原来的监听。这样,当通知中心转发某一消息时,如果同一对象多次注册了这个通知的观察者,则会收到多个通知。
2、observer 观察者(不能为nil,通知中心会弱引用,ARC是iOS9之前是unsafe_unretained,iOS9及以后是weak,MRC是assign,所以这也是MRC不移除会crash,ARC不移除不会crash的原因,建议都要严格移除。)
发送通知
/**
发送通知
@param notification 通知对象
*/
- (void)postNotification:(NSNotification *)notification;
/**
发送通知
@param aName 消息通知的名字
@param anObject 消息发送者
@param aUserInfo 传递的数据
*/
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
//发送通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"AAAA" object:nil];
移除通知
/**
移除通知,通过多条件移除通知
@param observer 观察者
@param aName 通知名字
@param anObject 通知发送者
*/
- (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方式的监听消息
/**
监听消息
@param name 消息通知的名字
@param obj 消息发送者(表示接收哪个发送者的通知,如果第四个参数为nil,接收所有发送者的通知)
@param queue 如果queue为nil,则消息是默认在post线程中同一线程同步处理;但如果我们指定了操作队列,就在指定的队列里面执行。
@param block block块会被通知中心拷贝一份(执行copy操作),需要注意的就是避免引起循环引用的问题,block里面不能使用self,需要使用weakSelf。
@return observer观察者的对象,最后需要[[NSNotificationCenter defaultCenter] removeObserver:observer];
*/
- (id )addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;
注意:
1、block块会被通知中心拷贝一份(执行copy操作),需要注意的就是避免引起循环引用的问题,block里面不能使用self,需要使用weakSelf。
2、一定要记得[[NSNotificationCenter defaultCenter] removeObserver:observer];
3、如果queue为nil,则消息是默认在post线程中同一线程同步处理;但如果指定了操作队列,就在指定的队列里面执行。
只监听一次的使用(消息执行完,在block里面移除observer)
//__block __weak id observer 可以在block里面移除
__block __weak id 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];
//发送通知
NSLog(@"发送通知的线程=====%@",[NSThread currentThread]);
[[NSNotificationCenter defaultCenter] postNotificationName:@"AAAA" object:nil];
}
- (void)aaaa {
NSLog(@"通知执行的方法线程=====%@",[NSThread currentThread]);
}
/*运行输出:
发送通知的线程====={number = 1, name = main}
通知执行的方法线程====={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(@"发送通知的线程--post前=====%@",[NSThread currentThread]);
[[NSNotificationCenter defaultCenter] postNotificationName:@"AAAA" object:nil];
NSLog(@"发送通知的线程--post后=====%@",[NSThread currentThread]);
});
}
- (void)aaaa {
NSLog(@"通知执行的方法线程=====%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:5];
}
/*
2017-10-25 17:21:13.847114+0800 SortDemo[18827:46789692] 发送通知的线程--post前====={number = 3, name = (null)}
2017-10-25 17:21:13.847352+0800 SortDemo[18827:46789692] 通知执行的方法线程====={number = 3, name = (null)}
2017-10-25 17:21:18.850760+0800 SortDemo[18827:46789692] 发送通知的线程--post后====={number = 3, name = (null)}
*/
得知:
1、执行监听方法是在发送通知的当前线程
2、发送通知与执行监听方法是同步的(发送通知会等待全部监听执行完)
所以关于界面的操作用到通知就需要注意:通知发送是什么线程?执行监听方法是不是耗时的任务?
处理同步问题的办法:
1、监听方法里面开线程或者异步执行
- (void)aaaa {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
});
}
2、NSNotificationQueue的NSPostASAP
NSLog(@"发送通知的线程--post前=====%@",[NSThread currentThread]);
NSNotification *notification = [NSNotification notificationWithName:@"AAAA"
object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification
postingStyle:NSPostASAP];
NSLog(@"发送通知的线程--post后=====%@",[NSThread currentThread]);
NSNotificationQueue
NSNotificationQueue给通知机制提供了2个重要的特性:
1、异步发送通知(上面的demo)
2、通知合并
NSPostingStyle和NSNotificationCoalescing
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1, (当runloop处于空闲状态时post)
NSPostASAP = 2, (当runloop能够调用的时候立即post)
NSPostNow = 3 (立即post,同步)
};
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
NSNotificationNoCoalescing = 0, (不合成)
NSNotificationCoalescingOnName = 1, (根据NSNotification的name字段进行合成)
NSNotificationCoalescingOnSender = 2 (根据NSNotification的object字段进行合成)
};
/**
队列发送通知
@param notification 通知对象
@param postingStyle 发送时机
@param coalesceMask 合并规则(可以用|符号连接,指定多个)
@param modes NSRunLoopMode 当指定了某种特定runloop mode后,该通知值有在当前runloop为指定mode的下,才会被发出(多线程使用时候,要开启线程的runloop且响应的mode)
*/
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray *)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,监听方法只执行一次,还有NSPostNow使用合并没有效果
}
- (void)aaaa:(NSNotification *)noti {
NSLog(@"=====%@",noti.object); // 接受到的信息是noti1的 (这个阶段第一个enqueueNotification)
}
所以:
1、当前runloop状态使用合并通知,监听方法只执行一次;
2、监听方法接受到信息是(当前runloop状态第一个enqueueNotification的信息object)
3、由于NSPostNow性质可知,不能用于通知合成。
系统的通知Name
// 当程序被推送到后台时
UIKIT_EXTERN NSNotificationName const UIApplicationDidEnterBackgroundNotification NS_AVAILABLE_IOS(4_0);
// 当程序从后台将要重新回到前台时
UIKIT_EXTERN NSNotificationName const UIApplicationWillEnterForegroundNotification NS_AVAILABLE_IOS(4_0);
// 当程序完成载入后通知
UIKIT_EXTERN NSNotificationName const UIApplicationDidFinishLaunchingNotification;
// 应用程序转为激活状态时
UIKIT_EXTERN NSNotificationName const UIApplicationDidBecomeActiveNotification;
// 用户按下主屏幕按钮调用通知,并未进入后台状态
UIKIT_EXTERN NSNotificationName const UIApplicationWillResignActiveNotification;
// 内存较低时通知
UIKIT_EXTERN NSNotificationName const UIApplicationDidReceiveMemoryWarningNotification;
// 当程序将要退出时通知
UIKIT_EXTERN NSNotificationName const UIApplicationWillTerminateNotification;
// 当系统时间发生改变时通知
UIKIT_EXTERN NSNotificationName const UIApplicationSignificantTimeChangeNotification;
// 当StatusBar框方向将要变化时通知
UIKIT_EXTERN NSNotificationName const UIApplicationWillChangeStatusBarOrientationNotification __TVOS_PROHIBITED; // userInfo contains NSNumber with new orientation
// 当StatusBar框方向改变时通知
UIKIT_EXTERN NSNotificationName const UIApplicationDidChangeStatusBarOrientationNotification __TVOS_PROHIBITED; // userInfo contains NSNumber with old orientation
// 当StatusBar框Frame将要改变时通知
UIKIT_EXTERN NSNotificationName const UIApplicationWillChangeStatusBarFrameNotification __TVOS_PROHIBITED; // userInfo contains NSValue with new frame
// 当StatusBar框Frame改变时通知
UIKIT_EXTERN NSNotificationName const UIApplicationDidChangeStatusBarFrameNotification __TVOS_PROHIBITED; // userInfo contains NSValue with old frame
// 后台下载状态发生改变时通知(iOS7.0以后可用)
UIKIT_EXTERN NSNotificationName const UIApplicationBackgroundRefreshStatusDidChangeNotification NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED;
// 受保护的文件当前变为不可用时通知
UIKIT_EXTERN NSNotificationName const UIApplicationProtectedDataWillBecomeUnavailable NS_AVAILABLE_IOS(4_0);
// 受保护的文件当前变为可用时通知
UIKIT_EXTERN NSNotificationName const UIApplicationProtectedDataDidBecomeAvailable NS_AVAILABLE_IOS(4_0);
// 截屏通知(iOS7.0以后可用)
UIKIT_EXTERN NSNotificationName const UIApplicationUserDidTakeScreenshotNotification NS_AVAILABLE_IOS(7_0);