iOS 通知基础介绍

简述

NSNotification 是iOS中一个消息通知类,存储消息的一些信息;
NSNotificationCenter 是一个通知中心,采用单例设计模式,用来发布、接收等消息操作的类。

NSNotification介绍

首先来看下,NSNotification类可以存储哪些消息信息。

// 消息的名称,操作对应的消息的依据,只读
@property (readonly, copy) NSNotificationName name;
// 消息对象,只读
@property (nullable, readonly, retain) id object;
// 存储消息信息的字典,只读
@property (nullable, readonly, copy) NSDictionary *userInfo;

从上面可以看出,NSNotification 类使用一个 userInfo 字典来存储消息信息的,并使用一个name字符串来标识消息。

如何创建一个消息呢?

实例方法:

// 参数分别为:消息名称、对象、消息字典
- (instancetype)initWithName:(NSNotificationName)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo;

系统后来又使用分类新增了两种快捷获取消息的类方法

+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject;
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

使用上面两种类方法可以省去我们手动分配空间的操作,可以根据业务需求选择其一即可;
注:系统明确指出不要调用 init 方法来初始化一个消息对象,而且NSNotification的属性name、object、userInfo都被设计为只读的,可能是出于消息安全考虑吧

NSNotificationCenter介绍

假如我们使用NSNotification类创建了一条消息,接下来如何发送该消息呢?这时就需要具有发布通知功能的类NSNotificationCenter(即通知中心)了。

注:在发布消息前,我们首先需要向通知中心一个注册一个消息监听者来接收我们将要发布的消息

首先获取通知中心

// [NSNotificationCenter defaultCenter]
@property (class, readonly, strong) NSNotificationCenter *defaultCenter;

向该通知中心注册一个监听者。

// 最常用的注册通知方法
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
// 注册通知的block形式
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;

如果我们创建好了一个通知 notification,就可以使用下面的方法发送通知。

- (void)postNotification:(NSNotification *)notification;

如果你并没有一个消息对象,你也可以在发布消息时直接创建。

// 只传递消息
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
// 数据字典一并发布
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

注:NSNotificationCenter 实现的过程实质是:某个对象向NSNotificationCenter注册需要接收某种消息,NSNotificationCenter便保存了这个对象的内存地址,当发布消息时,NSNotificationCenter就向该内存地址发送消息;那么假如这个对象内存已经被释放,即成为了僵尸对象,指向该地址的指针也就成了野指针,这时候当NSNotificationCenter将消息发送给该地址时,就会出现crash情况。这是在某个类使用通知的情况,但是在控制器中却不会crash,原因是控制器在调用dealloc时,会默默的帮我们移除通知。

但是,为了规范,一个addObserver就应该对应一个removeObserver。

移除观察者

// 移除observer上多有的注册
- (void)removeObserver:(id)observer;
// 移除指定的消息类型
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;

使用通知

使用通知比较简单一共分三步

步骤一:注册通知,新增消息类型以及接收者
步骤二:发送通知
步骤三:移除通知,即移除消息接收者

过程可以理解为:当某些个对象需要某种类型的消息时,便向NSNotificationCenter通知中心注册,NSNotificationCenter便将该消息和该对象绑定(保存该对象的内存地址),合适时机,NSNotificationCenter便发布消息,注册对象根据消息消息类型(name)选择性接收(name相同才接收),当注册对象内存被释放时,需要手动移除NSNotificationCenter中对应的注册对象。

应用情景:页面跳转时,我们给下一个页面发送一条消息

当前页面

- (void)viewDidLoad {
    [super viewDidLoad];
    // 对象参数
    UIButton *btn = [UIButton new];
    [btn setTitle:@"btn title" forState:UIControlStateNormal];
    // 信息字典
    NSDictionary *dic = @{@"name":@"lolita0164"};
    // 创建一则消息
    NSNotification *notification = [[NSNotification alloc] initWithName:@"lolita0164" object:btn userInfo:dic];
    
    // 模拟业务,延迟5秒发布消息
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 发布消息
        [[NSNotificationCenter defaultCenter] postNotification:notification];
    });
    
}

下一个页面 NextPageViewController.h

- (void)viewDidLoad {
    [super viewDidLoad];
    // 注册消息
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(noti:) name:@"lolita0164" object:nil];
}

// 消息处理
-(void)noti:(NSNotification *)noti{
    NSLog(@"%@接收到的消息:%@",[self class],noti.userInfo);
    UIButton *btn = noti.object;
    NSLog(@"%@接收到的消息:%@",[self class],btn.titleLabel.text);
}

// 对象释放时,移除该注册对象
-(void)dealloc{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"lolita0164" object:nil];
    NSLog(@"%@释放了",[self class]);
}

运行结果

通知例子

消息线程的选择

NSNotificationCenter消息的接受线程是基于发送消息的线程的,因此,有时候你发送的消息有时候可能不在主线程,而大家都知道操作UI必须在主线程,所以,在你收到消息通知的时候,注意选择你要执行的线程。

比如对于需要更新UI的通知,我们在主线程中更新UI

发送的消息非主线程中

dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(defaultQueue, ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:@"UIUpdate" object:nil];
    });

- (void)UIUpdate{
    if ([[NSThread currentThread] isMainThread]) {
        NSLog(@"main");
    } else {
        NSLog(@"not main");
    }
    // 主线程中更新UI
    dispatch_async(dispatch_get_main_queue(), ^{
        //更新UI操作
    });
}

输出结果:

非主线程中发送消息

来说下注册通知的block形式的使用,该方法让我们可以直接指定什么线程去完成消息处理。

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

示例

// 注册并接收处理消息
self.observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"lolita0164" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
    NSLog(@"%@接收到的消息:%@",[self class],note.userInfo);
    UIButton *btn = note.object;
    NSLog(@"%@接收到的消息:%@",[self class],btn.titleLabel.text);
}];

如果参数队列不指定,为nil,则表示和发送消息的线程一致。

注销操作

[[NSNotificationCenter defaultCenter] removeObserver:self.observer];
self.observer = nil; // 将该对象一并注销

注意事项

1、注册通知必须在发送通知之前

2、注册和发布消息的类型name要一致

3、消息接收对象必须存在,若不存在,则需要手动移除该消息接收对象

4、viewDidLoad里注册,dealloc中移除;或者viewWillAppear:里注册,viewWillDisappear:移除

5、移除通知最好指定消息类型name,否则可能移除掉其他对其他消息的监听

扩展

1、和KVO对比?

  • 两者都是属于观察者设计模式,但是通知更灵活,适用更广,比起KVO,通知可以用来监听状态的变化、键盘出现、app前后台的出现等,传递的数据也多种,不仅可以传递字典参数还可以传递对象参数
  • KVO通常用来监听某属性值的变化,它可以记录新旧值
  • 两者都是MVC模式下,实现UI和数据分离的手段

2、和delegate对比?

  • 通知使用起来更简洁,逻辑清晰
  • 通知适用于一对多的情况,它会给每一个存在的对象发布消息,这种广播式的发送消息意味着开销的增大
  • delegate适用于一对一的情况,明确知道delegate是谁的代理,而又是谁去实现代理协议
  • 两者的使用视情况而定,都是实现解耦的解决方案
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容