Objective-C 通知(NSNotification)及实现原理

简介

通知(NSNotification),是采用“观察者”模式来实现,发送通知的本身不需要知道使用者是谁,也不用知道有几个使用者,只需要在通知中心发送通知就可以实现消息的一个或者多个的传递。

以下是苹果对通知的定义:
https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/Notification.html#//apple_ref/doc/uid/TP40008195-CH35-SW1

A notification is a message sent to one or more observing objects to inform them of an event in a program. The notification mechanism of Cocoa follows a broadcast model. It is a way for an object that initiates or handles a program event to communicate with any number of objects that want to know about that event. These recipients of the notification, known as observers, can adjust their own appearance, behavior, and state in response to the event. The object sending (or posting) the notification doesn’t have to know what those observers are. Notification is thus a powerful mechanism for attaining coordination and cohesion in a program. It reduces the need for strong dependencies between objects in a program (such dependencies would reduce the reusability of those objects). Many classes of the Foundation, AppKit, and other Objective-C frameworks define notifications that your program can register to observe.

The centerpiece of the notification mechanism is a per-process singleton object known as the notification center (NSNotificationCenter). When an object posts a notification, it goes to the notification center, which acts as a kind of clearing house and broadcast center for notifications. Objects that need to know about an event elsewhere in the application register with the notification center to let it know they want to be notified when that event happens. Although the notification center delivers a notification to its observers synchronously, you can post notifications asynchronously using a notification queue (NSNotificationQueue).

Objective-C 通知(NSNotification)及实现原理_第1张图片

使用

比如现在有一个场景:当在用户成功登陆之后要在需要登陆的页面展示登陆成功后的样式:假设有个人中心控制器:ProfileViewController,登陆控制器:LoginViewController。在登录成功之后改变ProfileViewController的样式

  • 首先我们ProfileViewController中定义通知
-(void) viewDidLoad{
    [super viewDidLoad];
    //添加通知
    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(successLogin) name: @"SuccessLogin" object: nil];
    /*
    * defaultCenter,消息中心只有一个,通过类方法获取它的单例。
    * addObserver,添加监听者实例,此处为当前实例
    * selector,observer中的一个方法指针,当有消息的时候会执行此方法,并把相关上下文以参数传递过去
    * name,注册所关心消息的名称,
    * object,这个是对消息发送方的一个过滤,此参数据说明当前监听器仅对某个对象发出的消息感兴趣。
    */
    ...
    ...
}

//接收到通知之后调用此方法
-(void)successLogin{
    [self setLoginUI];
}

//在控制器销毁的时候移除通知
-(void)dealloc{
    //移除通知有两种:1.移除所有通知;2.移除指定通知
    //1.移除所有通知
    [[NSNotificationCenter defaultCenter]removeObserver:self]; 

    //2.移除指定通知 
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"SuccessLogin" object:nil];  

}
  • 在LoginViewController登录成功之后发送通知
-(void)successLogin{
    //登录成功之后发送通知
    [[NSNotificationCenter defaultCenter] postNotificationName:@"SuccessLogin" object:self userInfo:nil];
    /*
    * postNotificationName,推送消息的名称,匹配在注册消息监听者时的消息名称。 
    * object,发送消息的实例 
    * userInfo,发送消息时附带的消息上下文,我一般用到的都是字典格式(NSDictionary )
    */
}

原理

我们先分析系统通知的实现和使用的方法,我们在实践中一步一步优化。

1.首先获取通知对象使用单利:

[NSNotificationCenter defaultCenter];
//我们自己设计的类需要有一个单利方法来获取通知对象

2.注册通知:一系列的参数和方法

[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(successLogin) name: @"SuccessLogin" object: nil];

以上我们分析,注册通知需要4参数的方法:
Observer:需要传观察者;
selector:接收通知后执行方法;
name:通知的名称可以为空,只要有通知就执行;
object:字典类型可以为空
结合以上我们可以定义一个方法

- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
//nullable可以为空

此处查看文档还有一个方法

- (id )addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block NS_AVAILABLE(10_6, 4_0);
//此处发现name也是可以为空,为空就是为所有观察者发送通知

这个方法可以实现对通知的管理,queue:可以传入队列,不传就是同步执行,usingBlock:Block返回观察者对象,这里我们可以对通知进行管理比如只让观察者执行一次

__block __weak id observer = [[NSNotificationCenter defaultCenter] addObserverForName: @"SuccessLogin" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        //执行完成后移除通知
      [[NSNotificationCenter defaultCenter] removeObserver:observer];
   }];

3.发送通知

[[NSNotificationCenter defaultCenter] postNotificationName:@"SuccessLogin" object:self userInfo:nil];

需要提供发送通知方法,同时提供三个参数name,object,userInfo。object和userInfo为空,是发送给所有监听本name的观察者并且不带参数。

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

同时系统还提供另外两种发送通知的方法

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

4..移除通知

    //1.移除所有通知
    [[NSNotificationCenter defaultCenter]removeObserver:self]; 

    //2.移除指定通知 
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"SuccessLogin" object:nil];  

移除通知两个方法,移除全部,移除指定

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

5.通知模型
结合以上的方法的属性需求我们总结出一下属性:

@property (readonly, copy) NSNotificationName name;
@property (nullable, readonly, retain) id object;
@property (nullable, readonly, copy) NSDictionary *userInfo;

6.结合以上分析原理

尝试一:

第2步:注册通知的时候,把传进来的参数封装成模型存在数组;

第3步:发送通知的时候,我们便利数组,根据对应参数找到观察者们,然后执行观察者方法弟

第4步:移除通知,我们对数组操作即可。

考虑:系统通知在第2步中,会有队列和返回观察者本身的Block。我们的设计就必须考虑线程的调用和Block的回调。

分析

那么我们就必须必须在数组模型中加入队列标识,在第3步中加入判断,如有队列存在,我们把当前通知加入队列中执行。

还需要加入Block,执行打当前Block,并把观察者对象返回,

这个时候我们需要考虑分析循环引用,Block的管理,前面的博客中已近说明。

那么这个时候思路已经清晰,但是还需要考虑队列中通知的管理。

PS

通知的设计其中需要考虑的方面比较多,今天大多知识都在之前的博客中提到。提到队列,同步异步这也是我们设计的时候需要注意的,之后我会单独去总结一篇关于线程的博客,实现的代码不翼而飞了,那么之后会在GitHub中补上。进过上面的分析其实设计已近非常清晰了。但是只有踩过坑才知道,大家可以尝试一下,慢慢去改进自己的通知。

在开发中我们站在巨人的肩膀,我之前设计StatusHUD异常状态展示的时候,自己尝试了几种,控制器继承、网络层Context和单利类addSubView。之后借鉴SVProgressHUD和MBProgressHUD的思想设计了比较好用的StatusHUD,但是还有需要改进的地方。

你可能感兴趣的:(Objective-C)