OC--NSNotificationCenter重新认知

参考:
南峰子的技术博客:NSNotificationCenter
天口三水羊:NSNotification,看完你就都懂了
一文全解iOS通知机制(GNUStep源码去理解)

监听通知

方法 - (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;

参数解释:
1、observer 观察者(不能为nil,通知中心会弱引用,ARC是iOS9之前是unsafe_unretained,iOS9及以后是weak,MRC是assign,所以这也是MRC不移除会crash,ARC不移除不会crash的原因,建议都要严格移除。)
2、aSelector 收到消息后要执行的方法
3、aName 消息通知的名字(如果name设置为nil,则表示接收所有消息)
4、anObject 消息发送者(表示接收哪个发送者的通知,如果为nil,接收所有发送者的通知)

注意:
1、每次调用addObserver时,都会在通知中心重新注册一次,即使是同一对象监听同一个消息,而不是去覆盖原来的监听。这样,当通知中心转发某一消息时,如果同一对象多次注册了这个通知的观察者,则会收到多个通知。

代码例子:

 // 监听通知
 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(aaaa) name:@"AAAA" object:nil];
发送通知
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

参数解析
1、aName 消息通知的名字、
2、anObject 消息发送者、
3、aUserInfo 传递的数据

代码例子

 //发送通知
 [[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方式的监听消息

方法:- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;

参数解析:
1、name 消息通知的名字
2、obj 消息发送者(表示接收哪个发送者的通知,如果第四个参数为nil,接收所有发送者的通知)
3、queue 如果queue为nil,则消息是默认在post线程中同一线程同步处理;但如果我们指定了操作队列,就在指定的队列里面执行。
4、block block块会被通知中心拷贝一份(执行copy操作),需要注意的就是避免引起循环引用的问题,block里面不能使用self,需要使用weakSelf。
5、返回值,observer观察者的对象,最后需要[[NSNotificationCenter defaultCenter] removeObserver:observer];

注意:

1、block块会被通知中心拷贝一份(执行copy操作),需要注意的就是避免引起循环引用的问题,block里面不能使用self,需要使用weakSelf。
2、一定要记得[[NSNotificationCenter defaultCenter] removeObserver:observer];
3、如果queue为nil,则消息是默认在post线程中同一线程同步处理;但如果指定了操作队列,就在指定的队列里面执行。

代码例子

        //__block __weak id<NSObject> observer 可以在block里面移除
        // 只监听一次的使用(消息执行完,在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];
    
    //发送通知
    NSLog(@"发送通知的线程=====%@",[NSThread currentThread]);
    [[NSNotificationCenter defaultCenter] postNotificationName:@"AAAA" object:nil];
}

- (void)aaaa {
    NSLog(@"通知执行的方法线程=====%@",[NSThread currentThread]);
}


    /*运行输出:
    发送通知的线程=====<NSThread: 0x604000072f00>{number = 1, name = main}
    通知执行的方法线程=====<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(@"发送通知的线程--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前=====<NSThread: 0x60400027bac0>{number = 3, name = (null)}
    2017-10-25 17:21:13.847352+0800 SortDemo[18827:46789692] 通知执行的方法线程=====<NSThread: 0x60400027bac0>{number = 3, name = (null)}
    2017-10-25 17:21:18.850760+0800 SortDemo[18827:46789692] 发送通知的线程--post后=====<NSThread: 0x60400027bac0>{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、异步发送通知
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<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,监听方法只执行一次,还有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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容