OC--NSNotificationCenter重新认知

参考:
南峰子的技术博客: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);

你可能感兴趣的:(OC--NSNotificationCenter重新认知)