美文网首页iOS点点滴滴
OC--NSNotificationCenter重新认知

OC--NSNotificationCenter重新认知

作者: 啊哈呵 | 来源:发表于2017-10-25 19:44 被阅读197次

    参考:
    南峰子的技术博客: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 <NSObject>)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<NSObject> observer 可以在block里面移除
            __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、异步发送通知(上面的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<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);
    

    相关文章

      网友评论

        本文标题:OC--NSNotificationCenter重新认知

        本文链接:https://www.haomeiwen.com/subject/hzkqpxtx.html