简述
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是谁的代理,而又是谁去实现代理协议
- 两者的使用视情况而定,都是实现解耦的解决方案