Objective-C SDK之NSNotificationCenter

4,699 阅读9分钟
  1. 前言 在OC中有一种消息广播机制,该机制可以向注册到Notification Center的Observe发送消息。其流程可以简单用下图表示。

notification flow.png

  1. Observer注册到Notification Center
  2. Poster 发送消息到Notification Center
  3. Notification Center根据Poster发送的消息查找其维护的dispatch table
  4. 如果找到相对应的Observer需要的消息,那么将该消息广播至Observer
  5. Observer接受到消息,执行相对应的Action
  6. Notification Center将Observer移出 在消息通知机制中,需要关注三个角色Notification Center,Observer,Poster。
  • Notification Center是一个消息通知机制,可以将消息广播至注册的observer。
  • Observer 将其注册至Notification Center,可以接受相对应的消息,并且接收消息之后执行相对应的动作。
  • Poster 将消息发送至Notification Center。 需要注意的是每一个running app 都有一个名为defaultCenter的notification center,你也可以创建一个新的notificaion center。此外,notification center可以传递消息在一个程序中,也可以在多个进程中传递消息。如果需要在多个进程中传递通知,可以使用NSDistributedNotificationCenter

2. addObserver

向notification center中添加Observer有如下方法。

- (void)addObserver:(id)observer selector:(SEL)aSelector 
                    name:(NSNotificationName)aName 
                    object:(id)anObject;
observer:观察者对象
selector:接收消息后执行的方法,且该方法只有一个参数形如:-(void)acceptNotificationAction:(NSNotification *)noic;
name:消息名称,当为nil时,不作为搜索dispatch table的匹配属性。
obejct:接收哪个对象发送的消息,当为nil时,不作为搜索dispatch table的匹配属性。
- (id<NSObject>)addObserverForName:(NSNotificationName)name 
                 object:(id)obj 
                 queue:(NSOperationQueue *)queue 
                 usingBlock:(void (^)(NSNotification *note))block;
name:消息名称,当为nil时,不作为搜索dispatch table的匹配属性。
object:接收哪个对象发送的消息,当为nil时,不作为搜索dispatch table的匹配属性。
queue:指定block的运行的queue,当queue为nil时,block默认同步运行在poster所在线程
usingBlock:执行的block,可以理解为observer block。
该方法返回一个不透明的对象,用来代表observer。Notification Center保持该对象一直到移除observer registration。

当你使用addObserverForName:(NSNotificationName)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block方法注册observer,并且只接收一次消息。可以使用如下的代码。

NSNotificationCenter * __weak center = [NSNotificationCenter defaultCenter];
id __block token = [center addObserverForName:@"OneTimeNotification"
                                       object:nil
                                        queue:[NSOperationQueue mainQueue]
                                   usingBlock:^(NSNotification *note) {
                                       NSLog(@"Received the notification!");
                                       [center removeObserver:token];
                                   }];

3. removeObserver

将观察者从notification center中移除,并且不在接受消息通知,可以使用如下方法。

- (void)removeObserver:(id)observer 
                  name:(NSNotificationName)aName 
                object:(id)anObject;
observe:观察者对象,dispacth table中移除观察者对象为observer的条目。
aName:消息名称 dispacth table中移除消息名称为aName的条目。如果为nil,则不作为搜索dispatch table的匹配属性。
anObject:发送消息的对象,dispacth table中移除发送消息的对象为anObject的条目。如果为nil,则不作为搜索dispatch table的匹配属性。
- (void)removeObserver:(id)observer;
observer:观察者对象,dispacth table中移除观察者对象为observer的条目。

在你removeObserver的时候,必须保证addObserve方法中的指定对象存在。当你使用(iOS 9.0及以后,macOS 10.11及以后)addObserver:(id)observer selector:(SEL)aSelector name:(NSNotificationName)aName object:(id)anObject进行observer registration,你不需要使用removerObserver来unregister observer,系统会在下次对其发送消息时,会将其移除。 当你unregister observer时,如果你register observer时指定了消息名称以及消息的发送者,那么最好在unregister observer时最好使用下面的方法。

- (void)removeObserver:(id)observer 
                  name:(NSNotificationName)aName 
                object:(id)anObject;
observe:观察者对象,dispacth table中移除观察者对象为observer的条目。
aName:消息名称 dispacth table中移除消息名称为aName的条目。如果为nil,则不作为搜索dispatch table的匹配属性。
anObject:发送消息的对象,dispacth table中移除发送消息的对象为anObject的条目。如果为nil,则不作为搜索dispatch table的匹配属性。

4. postNotification

发送消息至notification center,有三种方法。其中最简单的方法是

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

第二种是指定消息名称,消息发送者,以及消息额外的信息。

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

最后一个相当于将第二种发送消息的方法的参数aUserInfo设置为nil。

- (void)postNotificationName:(NSNotificationName)aName 
                      object:(id)anObject;

5. 代码实践

定义如下两个类NotificationA和NotificationB。代码如下: 类NotificationA定义如下:

//NotificationA.h 

#ifndef NotificationA_h
#define NotificationA_h

@class NotificationB;

@interface NotificationA : NSObject

@property (weak) NotificationB *B;

- (void)removeObserver;
- (void)activeForNotification:(NSNotification *)notification;
- (void)postNotification;
- (void)addOberver;

@end

#endif /* NotificationA_h */

//NotificationA.m 

#import <Foundation/Foundation.h>
#import "NotificationA.h"
#import "NotificationB.h"

@implementation NotificationA

- (void)postNotification{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"A Send" object:self];
}

- (void)activeForNotification:(NSNotification *)notification{
    NSLog(@"Accept notificaion from B");
}

- (void)addOberver{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activeForNotification:) name:@"B Send" object:nil];
}

- (void)removeObserver{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"B Send" object:nil];
}

@end

类NotificationB定义如下:

// NotificationB.h

#ifndef NotificationB_h
#define NotificationB_h

@class NotificationA;

@interface NotificationB : NSObject

@property (weak) NotificationA *A;

- (void)removeObserver;
- (void)activeForNotification:(NSNotification *)notification;
- (void)postNotification;
- (void)addOberver;

@end

#endif /* NotificationB_h */

// NotificationB.m

@implementation NotificationB

- (void)postNotification{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"B Send" object:self];
}

- (void)activeForNotification:(NSNotification *)notification{
    NSLog(@"Accept notificaion from A");
}

- (void)addOberver{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activeForNotification:) name:@"A Send" object:self.A];
}

- (void)removeObserver{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"A Send" object:nil];
}

@end

两个类都有下面的四个方法。

- (void)removeObserver;
- (void)activeForNotification:(NSNotification *)notification;
- (void)postNotification;
- (void)addOberver;

首先利用NotificationA对象作为Observer,NotificationB作为Nofication Poster。 main函数如下:

int main(int argc, const char * argv[]) {
    NotificationA *A = [[NotificationA alloc] init];
    NotificationB *B = [[NotificationB alloc] init];
    A.B = B;
    B.A = A;
    
    [A addOberver];
    [B postNotification];
    return 0;
}

在类NotificationA中的addObserver中,register observer代码如下:name不为空,object为空。

- (void)addOberver{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activeForNotification:) name:@"B Send" object:nil];
}

在类NotificationB中的postNotification中,name不为空,object也不为空。

- (void)postNotification{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"A Send" object:self];
}

输出结果如下:可以看出,observer接收到了消息,执行了activeForNotification方法。

截屏2021-12-23 19.55.59.png 如果在类NotificationB中的postNotification中,name不为空,object也为空。

- (void)postNotification{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"A Send" object:nil];
}

输出结果如下:可以看出,observer接收到了消息,执行了activeForNotification方法。

截屏2021-12-23 19.59.14.png

当register observer时,指定了notification name时,当post notification时,无论指定或不指定notificaion poster。observer都能接受到消息

那么接着我们addobserver时指定发送消息的对象,且不指定notification name。代码如下: 类NotificationA的中addObserver函数如下:

- (void)addOberver{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activeForNotification:) name:nil object:self.B];
}

而此时类NotificationB的中postNotificaion函数如下:指定了object为self,notification name为nil。

- (void)postNotification{
    [[NSNotificationCenter defaultCenter] postNotificationName:“B Sendobject:self];
}

输出结果如下:可以看出,observer接收到了消息,执行了activeForNotification方法。

截屏2021-12-23 20.08.17.png 但是如果notification name为nil呢,虽然此时编译器只会给一个警告(该参数是一个non-null argument),但是observer已经接受不到消息。 代码如下:

- (void)postNotification{
    [[NSNotificationCenter defaultCenter] postNotificationName:nil object:self];
}

输出结果如下:可以看出,observer没有接收到消息。

截屏2021-12-23 20.10.53.png

如果postNotification方法没有指定或者与指定的notificaion poster不对应,也无法接受到消息。

- (void)postNotification{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"B Send" object:nil];
}
或
- (void)postNotification{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"B Send" object:[[NSString alloc] init]];
}

截屏2021-12-23 20.14.37.png

当register observer时,指定了object,没有指定notification name。当post notification时,notification name必须指定, 且poster object必须与register obberver指定object的一样,这样observer才能接受到消息

我们可以使用removerObserver来unregister Observer, 当removerObserver时指定了notification name,无论addObserver时,object是否指定。在dispatch table中包含该notification name的条目都会被remove。 代码如下: 类NotificationA中addObserver代码如下:

- (void)addOberver{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activeForNotification:) name:@"B Send" object:self.B];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activeForNotification:) name:@"B Send" object:nil];
}

类NotificationA中removeObserver方法代码如下:

- (void)removeObserver{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"B Send" object:nil];
}

类NotificationB中postNotification方法代码如下:

- (void)postNotification{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"B Send" object:[[NSString alloc] init]];
}

mian函数代码如下:

int main(int argc, const char * argv[]) {
    NotificationA *A = [[NotificationA alloc] init];
    NotificationB *B = [[NotificationB alloc] init];
    A.B = B;
    B.A = A;
    
    [A addOberver];
    [A removeObserve];
    [B postNotification];
    return 0;
}

结果如下:可以看出没有任何输出,说明removeObserver中指定了notification name,会将dispatch table中包含该notification name的条目都会被remove。

截屏2021-12-23 20.14.37.png

再看一个例子,当removerObserver时指定了poster object, 代码如下: 类NotificationA中addObserver代码如下:

- (void)addOberver{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activeForNotification:) name:nil object:self.B];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activeForNotification:) name:@"B Send" object:self.B];
}

类NotificationA中removeObserver方法代码如下:

- (void)removeObserver{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@“Test”object:self.B];
}

类NotificationB中postNotification方法代码如下:

- (void)postNotification{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"B Send" object:self]];
}

mian函数代码如下:

int main(int argc, const char * argv[]) {
    NotificationA *A = [[NotificationA alloc] init];
    NotificationB *B = [[NotificationB alloc] init];
    A.B = B;
    B.A = A;
    
    [A addOberver];
    [A removeObserve];
    [B postNotification];
    return 0;
}

结果如下:removeObserver没有生效,observer依然接收到了消息。

截屏2021-12-23 20.37.17.png 当removeObserver代码如下: 类NotificationA中removeObserver方法代码如下:

- (void)removeObserver{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:self.B];
}

结果如下:可以看出,observer被移除,且接受不到消息。那么removeObserver移除消息的原则是:指定了object和name,在dispatch table中搜索条目,这两项都一致的条目被移除。否则不移除。如果只指定name或object,则在dispatch table中寻找满足这一项的条目移除。 截屏2021-12-23 20.40.39.png 那Observer是不是也要在removerObserver中满足呢,将removeObserver代码改写如下:

- (void)removeObserver{
    [[NSNotificationCenter defaultCenter] removeObserver:[[NSString alloc] init] name:nil object:self.B];
}

结果如下:可以看出,如果removeObserver方法指定observer对象,在dispatch table没有找到对应的条目,则也无法移除observer。

截屏2021-12-23 20.46.46.png 总结:在dispatch table中包含三个匹配项:

  • Observer
  • Notification Name
  • Notification Poster 当register observer指定的这三项,remove Observer指定的三项满足才能在dispatch table中移除Observer,也就是Observer不能接受到消息。 当register observer指定的这三项,当你指定了notification name和notification 时时,postNotification指定的这两项需要保持一致。但只指定了notification 或 notification poster时,postnotification只需满足对应项一致,Observer就可以接受到消息。

6 总结

  1. notification center维持了一张dispatch table,其主要三项包括:Observer,Notification,Notification Poster。
  2. 当addObserver,指定了这三项时,removerObserver指定的这三项必须与addObserver指定的这三项一一对应才能从dispatch table移除。
  3. 当addObserver,指定了notification name和poster object不为空,则postNotification这两项都必须与其一致,Observer才能接受到消息。当只是指定了notificationname,poster object为空。则postNotification的notification name参数,必须与其一致,object参数则可以任意。当只指定了poster object。则postNotification的notification name(是一个non-null argument)必须指定,且poster obejct必须与其一致,Observer才能接受消息。