NSNotification学习笔记

一、添加通知监听者的方式

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

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

以上是添加通知监听者的两种方式

1. 添加监听者时的name与object

两种方式添加监听者传入的参数都包含name和object,这两个参数都可以为空,关于这两个参数主要有以下四种情况:

1.1 若是name和object同时为空则可以监听程序中所有的通知

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
      
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receviedNoti:) name:nil object:nil];

    [[NSNotificationCenter defaultCenter] addObserverForName:nil object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
        NSLog(@"block---%@",note);
    }];
}

- (void)receviedNoti:(NSNotification *)noti {
    NSLog(@"selector---%@",noti);
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"noti_one" object:nil userInfo:@{@"ncKey":@"ncValue"}];
}

添加了两个监听者,name与object都为空

2018-11-22 16:52:46.457900+0800 Nunca[50654:7785885] selector---NSConcreteNotification 0x60000025ae20 {name = UIViewAnimationDidStopNotification; object = ; userInfo = {
    delegate = "";
    name = "";
}}
2018-11-22 16:52:46.458099+0800 Nunca[50654:7785885] block---NSConcreteNotification 0x60000025ae20 {name = UIViewAnimationDidStopNotification; object = ; userInfo = {
    delegate = "";
    name = "";
}}
2018-11-22 16:53:03.266696+0800 Nunca[50654:7785885] selector---NSConcreteNotification 0x60400025e2d0 {name = _UIWindowSystemGestureStateChangedNotification; object = ; layer = >; userInfo = {
    "_UIWindowSystemGestureCancellingTouchesUserInfoKey" = 0;
}}
2018-11-22 16:53:03.266918+0800 Nunca[50654:7785885] block---NSConcreteNotification 0x60400025e2d0 {name = _UIWindowSystemGestureStateChangedNotification; object = ; layer = >; userInfo = {
    "_UIWindowSystemGestureCancellingTouchesUserInfoKey" = 0;
}}
2018-11-22 16:53:03.267779+0800 Nunca[50654:7785885] selector---NSConcreteNotification 0x60400025d610 {name = noti_one; userInfo = {
    ncKey = ncValue;
}}
2018-11-22 16:53:03.267996+0800 Nunca[50654:7785885] block---NSConcreteNotification 0x60400025d610 {name = noti_one; userInfo = {
    ncKey = ncValue;
}}

监听到了程序中所有发出来的通知,上面截取了一部分,最后打印的是点击事件中自己添加的。

1.2 若是name不为空、object为空则可以监听所有发送时通知名称与name相同的通知

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receviedNoti:) name:@"nc_noti_one" object:nil];

    [[NSNotificationCenter defaultCenter] addObserverForName:@"nc_noti_one" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
        NSLog(@"block---%@",note);
    }];
}


- (void)receviedNoti:(NSNotification *)noti {
    NSLog(@"selector---%@",noti);
}


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    [[NSNotificationCenter defaultCenter] postNotificationName:@"nc_noti_one" object:nil userInfo:@{@"ncKey_1":@"ncValue_1"}];
    
    [[NSNotificationCenter defaultCenter] postNotificationName:@"nc_noti_one" object:@"123" userInfo:@{@"ncKey_2":@"ncValue_2"}];
}

添加两个监听者,监听的name相同,object都为空,在点击屏幕时发送两条name相同的通知,但是一条object为空,一条不为空

2018-11-22 17:05:38.920806+0800 Nunca[50859:7801142] selector---NSConcreteNotification 0x604000253470 {name = nc_noti_one; userInfo = {
    "ncKey_1" = "ncValue_1";
}}
2018-11-22 17:05:38.921132+0800 Nunca[50859:7801142] block---NSConcreteNotification 0x604000253470 {name = nc_noti_one; userInfo = {
    "ncKey_1" = "ncValue_1";
}}
2018-11-22 17:05:38.921327+0800 Nunca[50859:7801142] selector---NSConcreteNotification 0x600000248610 {name = nc_noti_one; object = 123; userInfo = {
    "ncKey_2" = "ncValue_2";
}}
2018-11-22 17:05:38.921576+0800 Nunca[50859:7801142] block---NSConcreteNotification 0x600000248610 {name = nc_noti_one; object = 123; userInfo = {
    "ncKey_2" = "ncValue_2";
}}

监听者name不为空、object为空时,不管发送通知时有没有传入object,只要name对上了,就能被成功监听。

1.3 若是name为空、object不为空则可以监听所有发送通知时传入的object与监听传入的object为同一个对象的通知

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receviedNoti:) name:nil object:self];

    [[NSNotificationCenter defaultCenter] addObserverForName:nil object:@"123" queue:nil usingBlock:^(NSNotification * _Nonnull note) {
        NSLog(@"block---%@",note);
    }];
}


- (void)receviedNoti:(NSNotification *)noti {
    NSLog(@"selector---%@",noti);
}


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    [[NSNotificationCenter defaultCenter] postNotificationName:@"noti_name1" object:self userInfo:@{@"ncKey_1":@"ncValue_1"}];
    
    [[NSNotificationCenter defaultCenter] postNotificationName:@"noti_name2" object:@"123" userInfo:@{@"ncKey_2":@"ncValue_2"}];

}

添加了两个name为空的监听者,object一个为当前控制器,一个为字符串123
发送通知时name不可以为空

2018-11-22 17:19:32.028685+0800 Nunca[51024:7816220] selector---NSConcreteNotification 0x604000447440 {name = noti_name1; object = ; userInfo = {
    "ncKey_1" = "ncValue_1";
}}
2018-11-22 17:19:32.028979+0800 Nunca[51024:7816220] block---NSConcreteNotification 0x604000447440 {name = noti_name2; object = 123; userInfo = {
    "ncKey_2" = "ncValue_2";
}}

监听者name为空、object不为空时,只要发送通知时传入的object是与监听者的相同、不管发送通知的name是什么通知都能被成功监听。

1.4 若name与object都不为空,则只有发送时通知名称与name相同并且传入的object与监听传入的object为同一个对象的通知能被成功监听

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receviedNoti:) name:@"noti_name" object:self];

    [[NSNotificationCenter defaultCenter] addObserverForName:@"noti_name" object:@"123" queue:nil usingBlock:^(NSNotification * _Nonnull note) {
        NSLog(@"block---%@",note);
    }];
}


- (void)receviedNoti:(NSNotification *)noti {
    NSLog(@"selector---%@",noti);
}


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    [[NSNotificationCenter defaultCenter] postNotificationName:@"noti_name" object:self userInfo:@{@"ncKey_1":@"ncValue_1"}];
    
    [[NSNotificationCenter defaultCenter] postNotificationName:@"noti_name" object:@"123" userInfo:@{@"ncKey_2":@"ncValue_2"}];
    
    [[NSNotificationCenter defaultCenter] postNotificationName:@"noti_name" object:nil userInfo:@{@"ncKey_3":@"ncValue_3"}];

    [[NSNotificationCenter defaultCenter] postNotificationName:@"noti_name_x" object:self userInfo:@{@"ncKey_4":@"ncValue_4"}];
}

若监听者的name与object都不为空,那只有发送通知时传入的name与object都与监听者的相同通知才能被成功监听。

NSNotification学习笔记_第1张图片
关于监听者的name与object.png
2 通知发送与监听的线程

回到以上两种添加监听者的方法,上面列出来的是它们的一些共性,那他们的差异也是存在的。

2.1 queue为nil的时候
对于第一种传入selector方式,通知在哪个线程发送则在哪个线程被同步回掉。
对于第二种传入block形式的,可以传入一个NSOperationQueue,指定block被加入至哪个队列,若穿入的是空,则block会被在通知发送时所在线程同步调用(与第一种方式相同了)。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    NSLog(@"--add observer in thread: %@",[NSThread currentThread]);
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receviedNoti:) name:@"noti_name" object:nil];

    _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"noti_name" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
        NSLog(@"--block call back in thread: %@",[NSThread currentThread]);

    }];
    self.notiThread = [[NSThread alloc] initWithTarget:self selector:@selector(notiThreadStart) object:nil];
    [self.notiThread start];
}

- (void)receviedNoti:(NSNotification *)noti {
    NSLog(@"--selector call back in thread: %@",[NSThread currentThread]);
    
}

- (void)notiThreadStart {
    [[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
}


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    [self performSelector:@selector(notiThreadMethod) onThread:self.notiThread withObject:nil waitUntilDone:NO];

}

- (void)notiThreadMethod {

    NSLog(@"--post Noti in thread: %@",[NSThread currentThread]);

    [[NSNotificationCenter defaultCenter] postNotificationName:@"noti_name" object:self userInfo:nil];
    
    NSLog(@"--finish");
    
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver: _observer];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

在主线程添加了通知监听者(以第二种方式添加的监听者传入的queue为nil),然后在子线程发送通知。
另外可以看到第一种方式添加监听者时,监听者是我们自己传入指定的,第二种方式则是创建并返回了一个监听者,因此第二种需要我们额外保存返回的observer,以便于在何时的时机移除。

2018-11-22 18:10:29.882628+0800 Nunca[51489:7869385] --add observer in thread: {number = 1, name = main}
2018-11-22 18:10:35.204503+0800 Nunca[51489:7869593] --post Noti in thread: {number = 3, name = (null)}
2018-11-22 18:10:35.205316+0800 Nunca[51489:7869593] --selector call back in thread: {number = 3, name = (null)}
2018-11-22 18:10:35.205598+0800 Nunca[51489:7869593] --block call back in thread: {number = 3, name = (null)}
2018-11-22 18:10:35.205969+0800 Nunca[51489:7869593] --finish

可以看到,监听者接收到通知执行监听方法时所在的线程与通知发送所在线程一致,与添加监听者时所在的线程无关。另外,在通知发送后先去执行了监听者的方法之后再继续执行当前代码(打印--finish),说明通知的发送与监听是同步执行的。

2.2 指定queue的时候
对于第二种添加监听者的方式,也可以指定执行block所在queue,当监听到通知后要更新UI的时候就适合以这种方式添加监听者,此时将queue设置为mainQueue就好。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    NSLog(@"--add observer in thread: %@",[NSThread currentThread]);

    [[NSNotificationCenter defaultCenter] addObserverForName:@"noti_name" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        NSLog(@"--block call back in thread: %@",[NSThread currentThread]);
        
        for (int i = 0; i < 500; i++) {
            NSLog(@"--block--%d",i);
        }

    }];
    self.notiThread = [[NSThread alloc] initWithTarget:self selector:@selector(notiThreadStart) object:nil];
    [self.notiThread start];
}

- (void)notiThreadStart {
    [[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
}



- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    [self performSelector:@selector(notiThreadMethod) onThread:self.notiThread withObject:nil waitUntilDone:NO];

}

- (void)notiThreadMethod {

    NSLog(@"--post Noti in thread: %@",[NSThread currentThread]);

    [[NSNotificationCenter defaultCenter] postNotificationName:@"noti_name" object:self userInfo:nil];
    
    NSLog(@"--finish");
    
}

将queue指定为mainQueue

2018-11-22 18:30:39.598772+0800 Nunca[51794:7888240] --add observer in thread: {number = 1, name = main}
2018-11-22 18:30:41.612623+0800 Nunca[51794:7888393] --post Noti in thread: {number = 3, name = (null)}
2018-11-22 18:30:41.613826+0800 Nunca[51794:7888313] --block call back in thread: {number = 1, name = main}
2018-11-22 18:30:41.614457+0800 Nunca[51794:7888313] --block--0
2018-11-22 18:30:41.614660+0800 Nunca[51794:7888313] --block--1
2018-11-22 18:30:41.615330+0800 Nunca[51794:7888313] --block--2
2018-11-22 18:30:41.615687+0800 Nunca[51794:7888313] --block--3
...
2018-11-22 18:30:41.827388+0800 Nunca[51794:7888313] --block--497
2018-11-22 18:30:41.827685+0800 Nunca[51794:7888313] --block--498
2018-11-22 18:30:41.828318+0800 Nunca[51794:7888313] --block--499
2018-11-22 18:30:41.828621+0800 Nunca[51794:7888393] --finish

可以看到通知虽然在子线程被发送,但监听方法是在主线程执行,并且也是同步的,在监听方法中的所有任务执行完成之后才会继续原来的程序,因此也需要注意,发送通知时,很可能因为监听者需要处理耗时任务而使得通知发送所在的线程被阻塞。

二、存储observer的结构

首先根据GNUstep的代码,找出几个关键结构

typedef struct NCTbl {
  Observation       *wildcard;  /* Get ALL messages.        */
  GSIMapTable       nameless;   /* Get messages for any name.   */
  GSIMapTable       named;      /* Getting named messages only. */
  unsigned      lockCount;  /* Count recursive operations.  */
  NSRecursiveLock   *_lock;     /* Lock out other threads.  */
  Observation       *freeList;
  Observation       **chunks;
  unsigned      numChunks;
  GSIMapTable       cache[CACHESIZE];
  unsigned short    chunkIndex;
  unsigned short    cacheIndex;
} NCTable;

主要关注前三个:
1.Observation *wildcard;

typedef struct  Obs {
  id        observer;   /* Object to receive message.   */
  SEL       selector;   /* Method selector.     */
  struct Obs    *next;      /* Next item in linked list.    */
  int       retained;   /* Retain count for structure.  */
  struct NCTbl  *link;      /* Pointer back to chunk table  */
} Observation;

以链表的方式存储observers,里面存放的observer监听所有通知。

2.GSIMapTable nameless

struct  _GSIMapTable {
  NSZone    *zone;
  uintptr_t nodeCount;  /* Number of used nodes in map. */
  uintptr_t bucketCount;    /* Number of buckets in map.    */
  GSIMapBucket  buckets;    /* Array of buckets.        */
  GSIMapNode    freeNodes;  /* List of unused nodes.    */
  uintptr_t chunkCount; /* Number of chunks in array.   */
  GSIMapNode    *nodeChunks;    /* Chunks of allocated memory.  */
  uintptr_t increment;
#ifdef  GSI_MAP_EXTRA
  GSI_MAP_EXTRA extra;
#endif
};
struct  _GSIMapBucket {
  uintptr_t nodeCount;  /* Number of nodes in bucket.   */
  GSIMapNode    firstNode;  /* The linked list of nodes.    */
};
struct  _GSIMapNode {
  GSIMapNode    nextInBucket;   /* Linked list of bucket.   */
  GSIMapKey key;
#if GSI_MAP_HAS_VALUE
  GSIMapVal value;
#endif
};

当observer的name为空、object不为空的时候,则会被存入nameless。
以object为key,以observer的集合为value,构成一个MapNode结构,再以链表形式保存在nameless。


NSNotification学习笔记_第2张图片
nameless.png

3.GSIMapTable named;
当observer的name不为空时,observer被存入named。
以observer的name为key,value为一个集合(or链表结构),集合中存储的结构是以object为key、以对应的observers为value。


NSNotification学习笔记_第3张图片
named.png
添加observer时的具体步骤:

1.判断observer的name是否为空,如果不为空则进入named表,再以observer的name为key去named表中索引对应的下一层级的表,如果索引出来不存在则创建,取得下一层级的表的时候,再以observer的object为key去索引,object即使为空也可以索引到对应的集合(或者链表),将observer存入。
2.如果observer的name为空,则继续判断object是否为空,若object不为空,则以object为key去nameless表中查找对应的集合,并将observer存入
3.如果object也为空,则将observer添加至wildcard

移除observer时的具体步骤:

1.如果name与object都为空
1.1 先去wildcard去查找、删除
1.2 再继续步骤2与3
2.如果name为空
.......(有空再补充)
3.如果name不为空
.......(有空再补充)

三、发送通知的方式

1.直接post方式
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

这是最常见的发送通知的方式,上面的例子也全部基于此种通知发送的方式。这种方式发送的通知都是同步地去执行监听方法。

2.NSNotificationQueue
2.1NSNotificationQueue的结构
@interface NSNotificationQueue : NSObject {
@private
    id      _notificationCenter;
    id      _asapQueue;
    id      _asapObs;
    id      _idleQueue;
    id      _idleObs;
}
2.2获取NSNotificationQueue对象的方式
// 类对象 NSNotificationQueue.defaultQueue;
@property (class, readonly, strong) NSNotificationQueue *defaultQueue;
// 初始化NSNotificationQueue的指定方式
- (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter NS_DESIGNATED_INITIALIZER;

在同一线程中通过以上两种方式获取到的都是同一个NSNotificationQueue对象,即使多次调用initWithNotificationCenter,实际上也都是返回的同一个,只有切换线程,获取的才会是一个不一样的。
(这点与GNUstep的源码是有出路的,按照GNUstep中的实现每次initWithNotificationCenter应该都会生成一个新的queue对象,但是在Xcode9.2中运行,每次生成的都是相同的)

    NSNotificationQueue *queue = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
    NSNotificationQueue *queue1 = [NSNotificationQueue defaultQueue];
    NSNotificationQueue *queue3 = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];

    NSNotificationQueue *queue4 = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
2018-11-23 13:15:32.799013+0800 Nunca[56077:8460141] queue = 
2018-11-23 13:15:32.799013+0800 Nunca[56077:8460141] queue1 = 
2018-11-23 13:15:32.799673+0800 Nunca[56077:8460141] queue3 = 
2018-11-23 13:15:32.800445+0800 Nunca[56077:8460141] queue4 = 

每个线程存在并指存在一个与之对应的NSNotificationQueue对象。

2.3添加通知的方法
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray *)modes;

将通知加入队列中时,可以指定postingStyle、coalesceMask、modes

postingStyle

postingStyle决定通知发送的时机,有三种:
NSPostWhenIdle:空闲的时候发送
NSPostASAP:尽快发送,一般在runloop结束当前loop之后
NSPostNow :立刻发送,添加通知进队列地同步发送 (如果指定了合并策略会先进行通知合并)

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

- (void)receviedNoti:(NSNotification *)noti {
    NSLog(@"noti==%@",noti);
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSNotificationQueue *queue = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
    
    NSNotification *noti1 = [[NSNotification alloc] initWithName:@"noti_name" object:nil userInfo:@{@"noti":@"1"}];
    NSNotification *noti2 = [[NSNotification alloc] initWithName:@"noti_name" object:@"123" userInfo:@{@"noti":@"2"}];
    NSNotification *noti3 = [[NSNotification alloc] initWithName:@"noti_name" object:@"123" userInfo:@{@"noti":@"3"}];
    NSNotification *noti4 = [[NSNotification alloc] initWithName:@"noti_name" object:nil userInfo:@{@"noti":@"4"}];
    
    NSLog(@"enqueue noti1");
    [queue enqueueNotification:noti1 postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:nil];
    
    NSLog(@"enqueue noti2");
    [queue enqueueNotification:noti2 postingStyle:NSPostASAP coalesceMask:NSNotificationNoCoalescing forModes:nil];
    
    NSLog(@"enqueue noti3");
    [queue enqueueNotification:noti3 postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:@[@"123"]];
   
    NSLog(@"enqueue noti4");
    [queue enqueueNotification:noti4 postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:@[NSRunLoopCommonModes]];    
}


2018-11-23 14:32:04.065704+0800 Nunca[57044:8578090] enqueue noti1
2018-11-23 14:32:04.066063+0800 Nunca[57044:8578090] enqueue noti2
2018-11-23 14:32:04.066166+0800 Nunca[57044:8578090] enqueue noti3
2018-11-23 14:32:04.067676+0800 Nunca[57044:8578090] noti==NSConcreteNotification 0x600000448c10 {name = noti_name; object = 123; userInfo = {
    noti = 3;
}}
2018-11-23 14:32:04.067813+0800 Nunca[57044:8578090] enqueue noti4
2018-11-23 14:32:04.067985+0800 Nunca[57044:8578090] noti==NSConcreteNotification 0x6000004491e0 {name = noti_name; userInfo = {
    noti = 4;
}}
2018-11-23 14:32:04.068216+0800 Nunca[57044:8578090] noti==NSConcreteNotification 0x6000004490c0 {name = noti_name; object = 123; userInfo = {
    noti = 2;
}}
2018-11-23 14:32:04.068749+0800 Nunca[57044:8578090] noti==NSConcreteNotification 0x600000448f10 {name = noti_name; userInfo = {
    noti = 1;
}}
coalesceMask

coalesceMask提供了合并通知的功能,是NS_OPTIONS类型:
NSNotificationNoCoalescing :不合并
NSNotificationCoalescingOnName :将加入新通知之前已经存在queue中的name与新通知相同的通知合并(清除)
NSNotificationCoalescingOnSender :将queue中object与当前通知相同的通知合并,只留下当前加入的

除了在添加通知时指定通知的合并策略,也可以调用dequeueNotificationsMatching: coalesceMask:方法对queue中存在的通知进行清除。

调用enqueueNotification:方法时,如果指定了合并策略,底层其实也是在将通知添加进queue前先调用了一次dequeueNotificationsMatching: coalesceMask:方法。

    [queue enqueueNotification:noti4 postingStyle:NSPostNow coalesceMask:NSNotificationCoalescingOnName forModes:nil];
=====   这句与下面这两句代码 作用是一样的   =====
    [queue dequeueNotificationsMatching:noti4 coalesceMask:NSNotificationCoalescingOnName];
    [queue enqueueNotification:noti4 postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:nil];

将前面的代码修改一下,只在结尾处添加一个dequeueNotificationsMatching方法:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSNotificationQueue *queue = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
    
    NSNotification *noti1 = [[NSNotification alloc] initWithName:@"noti_name" object:nil userInfo:@{@"noti":@"1"}];
    NSNotification *noti2 = [[NSNotification alloc] initWithName:@"noti_name" object:@"123" userInfo:@{@"noti":@"2"}];
    NSNotification *noti3 = [[NSNotification alloc] initWithName:@"noti_name" object:@"123" userInfo:@{@"noti":@"3"}];
    NSNotification *noti4 = [[NSNotification alloc] initWithName:@"noti_name" object:nil userInfo:@{@"noti":@"4"}];
    
    NSLog(@"enqueue noti1");
    [queue enqueueNotification:noti1 postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:nil];
    
    NSLog(@"enqueue noti2");
    [queue enqueueNotification:noti2 postingStyle:NSPostASAP coalesceMask:NSNotificationNoCoalescing forModes:nil];
    
    NSLog(@"enqueue noti3");
    [queue enqueueNotification:noti3 postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:@[@"123"]];
   
    NSLog(@"enqueue noti4");
    [queue enqueueNotification:noti4 postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:@[NSRunLoopCommonModes]];
    
    [queue dequeueNotificationsMatching:noti2 coalesceMask:NSNotificationCoalescingOnName];
    
}
2018-11-23 14:35:01.444270+0800 Nunca[57093:8581368] enqueue noti1
2018-11-23 14:35:01.444611+0800 Nunca[57093:8581368] enqueue noti2
2018-11-23 14:35:01.445167+0800 Nunca[57093:8581368] enqueue noti3
2018-11-23 14:35:01.446550+0800 Nunca[57093:8581368] noti==NSConcreteNotification 0x600000241a70 {name = noti_name; object = 123; userInfo = {
    noti = 3;
}}
2018-11-23 14:35:01.446687+0800 Nunca[57093:8581368] enqueue noti4
2018-11-23 14:35:01.446859+0800 Nunca[57093:8581368] noti==NSConcreteNotification 0x600000241a40 {name = noti_name; userInfo = {
    noti = 4;
}}

在执行dequeueNotificationsMatching方法时,queue中存在noti1、noti2,由于noti1、noti2与用来匹配的noti2的name一致,因此都被dequeue了。

modes

modes可以指定通知在runloop的哪几种mode中执行,但是对于postingStyle为NSPostNow的,即使runloop不是运行在指定mode,通知也会在当前mode立刻被发送。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSNotificationQueue *queue = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
    
    NSNotification *noti1 = [[NSNotification alloc] initWithName:@"noti_name" object:nil userInfo:@{@"noti":@"1"}];
    NSNotification *noti2 = [[NSNotification alloc] initWithName:@"noti_name" object:@"123" userInfo:@{@"noti":@"2"}];
    NSNotification *noti3 = [[NSNotification alloc] initWithName:@"noti_name" object:@"123" userInfo:@{@"noti":@"3"}];
    NSNotification *noti4 = [[NSNotification alloc] initWithName:@"noti_name" object:nil userInfo:@{@"noti":@"4"}];
    
    NSLog(@"enqueue noti1");
    [queue enqueueNotification:noti1 postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:@[@"123"]];
    
    NSLog(@"enqueue noti2");
    [queue enqueueNotification:noti2 postingStyle:NSPostASAP coalesceMask:NSNotificationNoCoalescing forModes:@[NSDefaultRunLoopMode]];
    
    NSLog(@"enqueue noti3");
    [queue enqueueNotification:noti3 postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:@[@"123"]];
   
    NSLog(@"enqueue noti4");
    [queue enqueueNotification:noti4 postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:@[NSRunLoopCommonModes]];
    
}
2018-11-23 14:40:49.655868+0800 Nunca[57168:8589299] enqueue noti1
2018-11-23 14:40:49.656529+0800 Nunca[57168:8589299] enqueue noti2
2018-11-23 14:40:49.657081+0800 Nunca[57168:8589299] enqueue noti3
2018-11-23 14:40:49.658542+0800 Nunca[57168:8589299] noti==NSConcreteNotification 0x600000447cb0 {name = noti_name; object = 123; userInfo = {
    noti = 3;
}}
2018-11-23 14:40:49.658682+0800 Nunca[57168:8589299] enqueue noti4
2018-11-23 14:40:49.658838+0800 Nunca[57168:8589299] noti==NSConcreteNotification 0x6000004477d0 {name = noti_name; userInfo = {
    noti = 4;
}}
2018-11-23 14:40:49.659112+0800 Nunca[57168:8589299] noti==NSConcreteNotification 0x600000447c20 {name = noti_name; object = 123; userInfo = {
    noti = 2;
}}
2.4接收通知后监听方法执行的线程

对于利用NSNotificationQueue发送通知的方式,监听方法执行的线程与通知被加入至队列时所在的线程一致。

四、总结

使用通知时的注意点:
1.使用 addObserver: selector: 添加监听者时,监听方法执行时所在线程与发送通知所在的线程一致,若是在监听的方法中添加处理UI或其他需要指定线程处理的任务,则需要注意。
2.postNotif 方式发送通知时,会同步去处理监听者的回掉,如果回掉中有耗时任务,则容易阻塞当前线程。
3.iOS9以后不移除observer也不会导致程序崩溃,但最好当然是要用完就移除啦。
4........(想到再补充)

主要参考:

深入理解iOS NSNotification
浅谈 iOS NSNotification

你可能感兴趣的:(NSNotification学习笔记)