NSNotificationCenter使用总结

通知中心对于iOS开发者最熟悉不过了,它实现了一对多的消息传递,可以实现跨页面传递。NSNotificationCenter的主要方法有以下几种:


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

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

- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;

- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block NS_AVAILABLE(10_6, 4_0);
    // The return value is retained by the system, and should be held onto by the caller in
    // order to remove the observer with removeObserver: later, to stop observation.

其中最后一个方法返回 NSObserver 对象,在使用此方法添加观察者的时候,需要注意循环引用和 remove 的方式。每种方法在不同系统上的表现也不尽相同。

一、添加 observer 的方法

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

此方法中的 observer 是弱引用,直接使用self等不会造成强引用,此处提示一点,如果同一个通知连着添加n次,那SEL的方法会调用n次,所以要确保 addObserver 和 removeObserver 成对的出现。例如:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"test" object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"test" object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"test" object:nil];
}

- (void)test
{
  
}

当发送 test 通知的时候,test 方法会被调用三次。

2、Block方式
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block NS_AVAILABLE(10_6, 4_0);
    // The return value is retained by the system, and should be held onto by the caller in
    // order to remove the observer with removeObserver: later, to stop observation.

此方法需注意一下几点:
1) 这个方法会返回一个 NSObserver 对象,这个对象被系统强持有,调用者需要持有这个对象,用于停止通知移除观察者时使用。这种方式添加的通知,如果不持有这个对象,是无法彻底销毁这个通知的,具体做法如下:

  @property (nonatomic, strong) id observer;

  - (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    // 添加通知观察者
    _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"test" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        
    }];
  }      

  - (void)dealloc {
      if (_observer) {
          [[NSNotificationCenter defaultCenter] removeObserver:_observer];
          _observer = nil;
      }
  }

2)这个方法中的 block 会强持有对象,所以在block中的成员变量和属性,需要进行弱引用,如果没有弱引用,则需要在 dealloc 函数之前对通知进行移除,否则不会执行 dealloc 函数。

  @property (nonatomic, strong) id observer;

  - (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // 需要进行弱引用
    __weak typeof(self) weakSelf = self;
    // 添加通知观察者
    _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"test" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        weakSelf.label.backgroundColor = [UIColor redColor];
    }];
  }      

  - (void)dealloc {
      if (_observer) {
          [[NSNotificationCenter defaultCenter] removeObserver:_observer];
          _observer = 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、此处需要注意发送通知时,所在的线程,如果在子线程发送通知,那么通知的接收也是在子线程。
2、发送通知中的object参数要注意,这个和注册时候使用的方式有些关系,注册方法里,也有一个object参数,举个栗子:

// 注册
  self.name = @"Tom";
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test1) name:@"lala" object:_name];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test2) name:@"lala" object:nil];

上边两个注册通知的名字都是 lala ,唯一不同的是一个 object 是 nil ,一个是 _name。下面以两种不同的方式去发送通知:
第一种:

[[NSNotificationCenter defaultCenter] postNotificationName:@"lala" object:nil];

这种方式发送通知的时候,只有 test2 方法会被调用。
第二种:

[[NSNotificationCenter defaultCenter] postNotificationName:@"lala" object:_name];

这种方法发送通知的时候,test1 和 test2 都会被调用。如果此时我把 _name 改了会出现什么情况呢?

_name = @"lala";
[[NSNotificationCenter defaultCenter] postNotificationName:@"lala" object:_name];

此时只有 test2 被调用,因为 _name 的地址被更改了,所以通知匹配不上了,导致 test1 无法被识别到了。以上可以说明如果添加通知的时候传入了 object 参数,那么发送通知时,就会匹配 name 和 object 两个条件。如果没传 object 参数,则只匹配 name。

突然发现移除方法里也有这个 object 参数, 为了思路的连贯,移除通知的方法写在这里。
按照上面的方法,添加了两个通知,一个携带 object参数,一个不携带 object参数,先按照以下操作移除:

[[NSNotificationCenter defaultCenter] removeObserver:self name:@"lala" object:_name];

此时的结果是 test1 方法通知被移除了,但是 test2 方法并没有被移除掉。如果在移除之前,修改了 _name 呢?

_name = @"lala ";
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"lala" object:_name];

会发现此时效果是一样的,即使 _name 的地址发生了变化,但是 test1 的通知还是被移除了,test2 的通知没有被移除。如果按照以下方法移除:

[[NSNotificationCenter defaultCenter] removeObserver:self name:@"lala" object:nil];

此时 test1 和 test2 都被移除掉了。如果使用

[[NSNotificationCenter defaultCenter] removeObserver:self];

效果是一样的,不同之处在于这个方法不止会移除掉自己添加的通知方法,同时也会移除掉系统的通知方法,所以除非是这个对象要被释放掉,不要轻易使用这种方法进行移除通知。

总结一下:

1、如果添加通知的时候传入了 object 参数,那么发送通知时,就会匹配 name 和 object 两个条件。如果没传 object 参数,则只匹配 name。
2、如果中途修改 object 对象,那么通过 [[NSNotificationCenter defaultCenter] postNotificationName:@"lala" object:_name];这种方式发送的通知将会失效。
3、移除通知的时候,如果remove中的 name 和 object 都存在, 那么以这种方式初始化的并且 name 一致,都会被移除掉。 如果 remove 中只有 name, 那么这个 name 下的所有通知都会被移除。
4、[[NSNotificationCenter defaultCenter] removeObserver:self];不但会移除自己添加的通知,同时也会移除系统自动添加的通知,所以除非是类要被释放掉,不然谨慎使用这个方法。

三、移除通知的方法

在发送通知中已经进行了说明,移除通知主要有以下两种方法:

- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;

四、系统的差异

为 NSNotificationCenter 添加分类,hook 掉系统的移除通知方法

+ (void)load
{
    Method originRemoveM = class_getInstanceMethod([self class], @selector(removeObserver:));
    Method myRemoveM = class_getInstanceMethod([self class], @selector(my_removeObserver:));
    method_exchangeImplementations(originRemoveM, myRemoveM);
}

- (void)my_removeObserver:(id)observer
{
    NSLog(@"移除通知 -> observer = %@", observer);
    
    [self my_removeObserver:observer];
}

通过Log打印,我们会发现,在iOS8以上的系统,ViewController类在 dealloc 之后,自动调用removeObserver 方法。

在 iOS9 系统之后,对象释放的时候,如果使用 SEL 方式进行添加,如果不移除通知,也不会有什么影响。如果 iOS9 系统之前,如果对象释放时候不进行移除,那么当对象释放之后,再发送通知,就有可能造成野指针的崩溃。所以还是建议进行移除。
如果使用 Block 的方式进行添加的,一定要持有对象,在释放的时候,自己手动进行释放操作,不然会出现对象释放了,但是发送通知的时候,block还是会被调用,此时 bolck 中的 self 对象已经是 nil 了。

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

推荐阅读更多精彩内容

  • 1.objective-c常见面试题:1、**OC **语言的基本特点OC 语言是 C 语言的一个超集,只是在 C...
    LZM轮回阅读 957评论 0 3
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 点击查看原文 Web SDK 开发手册 SDK 概述 网易云信 SDK 为 Web 应用提供一个完善的 IM 系统...
    layjoy阅读 13,662评论 0 15
  • 水越发的凉了 碗莲开出了清冷的味道
    陌上羽落听弦生阅读 543评论 2 13
  • 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上n级台阶一共有多少种方法?思路:假设1级台阶有f(1...
    江小修阅读 788评论 0 0