[Cocoa]_[初级]_[NSNotificationCenter通知中心发布接收消息注意事项]

场景

  1. macOS 下进行 Cocoa 开发时,我们偶尔会需要监听窗口的大小改变的通知,以便能控制子窗口或者 NSView 改变大小. 比如 NSWindowDidResizeNotification 通知. 那么我们应该如何监听父窗口的大小改变通知?

  2. 如果我们使用 NSNotificationCenter 进行 postNotificationName 消息, 这个消息发送到接受者是异步还是同步的?

说明

  1. Cocoa 包括两种类型的通知中心. NSNotificationCenter 管理同进程的通知, 而 NSDistributedNotificationCenter 管理在同一个系统里的跨进程的通知.

  2. 这里我们开发界面程序,在同一个进程里的通知都是通过 NSNotificationCenter 管理. 每个进程都有一个默认的通知中心,这个通知中心你可以通过 [NSNotificationCenter defaultCenter] 类方法来获取. 这个通知中心处理同一个进程里通知. 这个通知中心的设计模式就是我们常听说的 观察者模式 或者说是 发布订阅模式. 我在这篇文章里有说观察者模式在项目中实际使用例子2

  3. NSNotificationCenter 使用通知有以下注意点:

    1. 通知中心发送通知给观察者是同步的. 即当 postNotificationName 发布一个通知调用,控制器(通知中心)不会返回,直到所有的观察者已经接收并处理这些消息. 如果想发送异步通知,那么需要使用一个通知队列 NSNotificationQueue, 这里不做细讲.
    2. 在多线程程序, 通知往往可以在某个线程发出,而和观察者注册通知处理方法的线程不一样是可以的。
    3. 每一个线程都有默认的通知队列(notification queue),它关联着进程的通知中心。这个队列用来维护通知实例 NSNotification。 这个通知队列遵守 FIFO 顺序,依次把通知转发给通知中心,而通知中心把这个通知发送给注册的观察者.
    4. 如果工作线程(非界面线程) 发出一个通知给通知中心,而观察者的处理方法却调用了界面绘制相关操作,比如设置文本值,那么可能会导致程序崩溃,因为界面元素需又界面线程操作,所以接收的通知是在界面操作,那么这个postNotificationName 必须要在界面线程调用(即主线程).
  4. 使用完通知要把它从通知中心移除,不然这个通知会一直存在造成以下问题:

    1. 无效的通知中心观察者存在内存泄漏.
    2. 外部如果发送未被移除的通知, 而观察者已经销毁,那么程序会崩溃.
  5. 观察者必须是一个 Object-C 对象, 以下 addObserver 方法必须有值,不能是 nil.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveMessage:) name:kBookOpenNotification object:nil];

例子

  1. 以下是一个命令行程序演示了 NSNotificationCenter 最常用的同步发送通知.
//
//  main.m
//  test-notification
//
//  Created by sai on 5/2/20.
//  Copyright (c) 2020 tobey. All rights reserved.
//

#import <Foundation/Foundation.h>

static NSString* kBookCancelNotification = @"BookCancelNotification";
static NSString* kBookOpenNotification = @"BookOpenNotification";
static BOOL shouldKeepRunning = NO;


@interface Book: NSObject


@end


@implementation Book

-(void)receiveMessage:(NSNotification*)data
{
    NSDictionary* dic = data.object;
    NSLog(@"%@ data is %@",data.name,dic);
}

-(id)init
{
    NSLog(@"Book init");
    self = [super init];
    // object 为nil时,接收任何位置发的通知.
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveMessage:) name:kBookCancelNotification object:nil];
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveMessage:) name:kBookOpenNotification object:nil];
    
    return self;
}

-(void)dealloc
{
    NSLog(@"Book dealloc");
    // 使用完通知要把它从通知中心移除,不然这个通知会一直存在造成以下问题:
    // 1.无效的通知中心观察者存在内存泄漏.
    // 2.外部如果发送 kBookCancelNotification 消息,而BOOK已经销毁,那么程序会崩溃.
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

@end


void testNotification()
{
    NSLog(@"================ testNotification BEGIN ================");
    @autoreleasepool {
        Book* book = [Book new];
        NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:@"https://blog.csdn.net/infoworld",@"blog",nil];
        
        NSLog(@"== postNotificationName kBookCancelNotification ==");
        [[NSNotificationCenter defaultCenter] postNotificationName:kBookCancelNotification object:dict userInfo:nil];
        
        NSDictionary* dict1 = [NSDictionary dictionaryWithObjectsAndKeys:@"Tobey",@"name",nil];
        NSLog(@"== postNotificationName kBookOpenNotification ==");
        [[NSNotificationCenter defaultCenter] postNotificationName:kBookOpenNotification object:dict1 userInfo:nil];
    }
    
    // 1.如果Book dealloc 里没有调用 removeObserver,那么以下代码调用即会崩溃.
    // 2.如果Book dealloc 里调用了removeObserver,那么消息中心没有对应的处理观察者,会丢弃这个通知.
    NSDictionary* dict2 = [NSDictionary dictionaryWithObjectsAndKeys:@"Tobey",@"name",nil];
    [[NSNotificationCenter defaultCenter] postNotificationName:kBookOpenNotification object:dict2 userInfo:nil];
    
    NSLog(@"================ testNotification END ================");
}

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        
        // insert code here...
        NSLog(@"Hello, World!");
        testNotification();
        dispatch_async(dispatch_get_main_queue(),^(){
            shouldKeepRunning = NO;
        });
        
        NSRunLoop *theRL = [NSRunLoop currentRunLoop];
        while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
    }
    return 0;
}


输出

2020-05-02 23:48:52.950 test-notification[896:303] Hello, World!
2020-05-02 23:48:52.952 test-notification[896:303] ================ testNotification BEGIN ================
2020-05-02 23:48:52.953 test-notification[896:303] Book init
2020-05-02 23:48:52.953 test-notification[896:303] == postNotificationName kBookCancelNotification ==
2020-05-02 23:48:52.954 test-notification[896:303] BookCancelNotification data is {
    blog = "https://blog.csdn.net/infoworld";
}
2020-05-02 23:48:52.954 test-notification[896:303] == postNotificationName kBookOpenNotification ==
2020-05-02 23:48:52.955 test-notification[896:303] BookOpenNotification data is {
    name = Tobey;
}
2020-05-02 23:48:52.955 test-notification[896:303] Book dealloc
2020-05-02 23:48:52.956 test-notification[896:303] ================ testNotification END ================

场景

Notification Programming Topics

观察者模式在项目中实际使用例子2

观察者模式在项目中实际使用例子

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Peter(阿斯拉达)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值