【iOS】通知的底层原理(实现)

23 篇文章 0 订阅
19 篇文章 0 订阅

通知

一. 通知的基本使用

1. 基本概念

NSNotification 是iOS中一个调度消息通知的类,采用单例模式设计,在程序中实现传值、回调等地方应用很广。在iOS中,NSNotification & NSNotificationCenter是使用观察者模式来实现的用于跨层传递消息。

概要:

    1. 观察者和被观察者都无需知晓对方,只需要通过标记在NSNotificationCenter中找到监听该通知所对应的类,从而调用该类的方法
    1. 并且在NSNotificationCenter中,观察者可以只订阅某一特定的通知,并对其做出相应操作,而不用对某一个类发的所有通知都进行更新操作
    1. NSNotificationCenter对观察者的调用不是随机的,而是遵循注册顺序一一执行的,并且在该线程内是同步的

2. 什么情况下使用通知

观察者模式:定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。

应用场景:

  1. 对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变。
  2. 一个对象必须通知其他对象,而它又不需要知道其他对象是什么

3. 如何使用通知

  1. 在接收参数的地方注册通知,并实现定义方法:
    向观察者中心添加观察者(2种方式)

    //观察者接收到通知后执行任务的代码在发送通知的线程中执行
    addObserver:selector:name:object:
  
    //观察者接收到通知后执行任务的代码在指定的操作队列中执行
    addObserverForName:object:queue:usingBlock:


  1. 创建一个通知
//方法一:不带object信息
NSNotification *notification = [NSNotification notificationWithName:@"MyNotification" object:self];

//方法二:带object信息 
NSDictionary *userInfo = @{@"key1": @"value1", @"key2": @"value2"};
NSNotification *notification = [NSNotification notificationWithName:@"MyNotification" object:self userInfo:userInfo];
  1. 在要传递参数的地方,发送通知给通知中心通知中心
    
    //先创建好通知,直接发送通知
    postNotification:
    //不用先创建通知,这个方法自己就创建并发送了 
    postNotificationName:object:
    postNotificationName:object:userInfo:


  1. 观察者接收到消息执行相应的行为

  2. 在通知中心移除观察者


removeObserver:
removeObserver:name:object:

4. 使用通知需要注意哪些细节

  1. 通知一定要移除,在dealloc方法里面移除
  2. 通知有同步通知和异步通知,只不过我们同步通知用的比较多
  3. 不能用-(instancetype)init初始化一个通知(因为没有这个方法)。通知有自己的两种初始化方法。

同步通知和异步通知:
同步通知:

- (IBAction)sendSyncNotification:(id)sender {
    // 发送同步通知
    [[NSNotificationCenter defaultCenter] postNotificationName:@"SyncNotification" object:nil];
}

异步通知 :

- (IBAction)sendAsyncNotification:(id)sender {
    // 在后台线程发送异步通知
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 执行耗时的操作
        sleep(2);
        
        // 发送异步通知
        [[NSNotificationCenter defaultCenter] postNotificationName:@"AsyncNotification" object:nil];
    });
}

二. 通知的实现原理

  1. 概述:首先,信息的传递是依靠(NSNotification),也就是说,通知就是信息(执行的方法,观察者本身(self),参数)的包装。通知中心(NSNotificationCenter)是一个单例,向通知中心注册观察者,也就是说,这个通知中心有个集合,这个集合存放着观察者。那么这个集合是什么样的数据类型?可以这么思考:发送通知需要name参数,添加观察者也有一个name参数,这个两个name一样的时候,当发送通知的时候,观察者对象就能接受到信息,执行对应的操作。那么这个集合很容易想到就是NSDictionary!key就是name,value就是NSArray(存放数据模型),里面存放观察者对象。如下图

在这里插入图片描述

当发送通知时,在通知中心的字典,根据name找到value,这个value就是一个数组,数组里面存放数据模型(observer,SEL)。即可执行对应的行为。

  1. 自定义实现通知功能

首先创建一个自定义文件NotificationCenter,继承自NSObject,用做自定义通知的类,因为通知是可以实现多对多关系的,所以我们在这个类中还需要定义一个可变的字典属性,用来存储注册的通知。又因为注册的通知数据需要一直保存下来,所以我们使用单例来完成这一操作,保证我们在想要访问已经注册的通知的时候,其数据是存在的。


@interface NotificationCenter ()

// 因为通知是多对多的关系,所以这里定义一个可变字典用来存储对应关系
@property (nonatomic, strong) NSMutableDictionary *classDictionary;

@end
@implementation NotificationCenter

// 实现默认的通知中心,是个单例,防止其自动销毁
+ (instancetype)defaultCenter {
    // 定义一个锁
    static dispatch_once_t onceToken;
    // 创建通知中心的单例,同时初始化其中数据
    static NotificationCenter *notificationCenter = nil;
    dispatch_once(&onceToken, ^{
        notificationCenter = [NotificationCenter new];
        notificationCenter.classDictionary = [NSMutableDictionary dictionary];
    });
    return notificationCenter;
}
@end

因为我们使用通知的目的就是为了传递参数,供别的类来使用,所以我们这里再定义一个专门保存通知的类ZJQNotification,其中包含通知的必要信息:通知的名称、通知传递的参数信息、以及一个id类型的object。因为这是需要给外界透露的接口,外界不能对其进行写操作,所以为只读(readOnly)属性,除了这三个参数,当然还得需要一个快速的初始化类的方法:


// 通知类,用来保存通知及其参数
@interface ZJQNotification : NSObject

// 对外有三个只读属性
@property (readonly, copy) NSNotificationName name; // 通知名
@property (nullable, readonly, copy) NSDictionary *userInfo; // 参数信息
@property (nullable, readonly, retain) id object; // 接收通知的对象

// 快速初始化类参数的方法
- (instancetype)initWithName:(NSString *)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo;
@end

该通知类中的三个参数是对外只读的,内部可以进行修改,所以我们在内部重写属性,实现可读可写,同时实现该类快速初始化方法:


@interface ZJQNotification()
// 在内部可修改这三个属性
@property (nonatomic, copy) NSString *name; // 通知名
@property (nonatomic, copy) NSDictionary *userInfo; // 参数信息
@property (nonatomic, retain) id object; // 接收通知的对象

@end
 
// 通知类,用来保存通知及其参数
@implementation ZJQNotification

// 快速初始化类参数的方法
- (instancetype)initWithName:(NSString *)name object:(id)object userInfo:(NSDictionary *)userInfo {
    ZJQNotification *notification = [ZJQNotification new];
    notification.name = name;
    notification.object = object;
    notification.userInfo = userInfo;
    return notification;
}

@end

接着我们在之前定义的通知中心ZJQNotificationCenter中再定义通知常用的对外开放的接口,注册通知、发送通知、删除通知:


// 通知中心
@interface ZJQNotificationCenter : NSObject

// 默认的通知中心
+ (instancetype)defaultCenter;

// 添加通知中心
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject;

// 发送通知
- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSString *)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;

@end

接着就一个一个实现,首先是添加通知的逻辑,我们在添加、调用通知的时候,需要知道三个必要信息,一个是创建该通知的实例类,一个是通知要调用的自定义方法,以及一个object。所以我们需要将这三个信息保存起来,一起保存到刚刚我们创建的通知中心单例的classDictionary属性中,为了方便我们后续的查找,所以该属性的key我们使用注册通知时通知的名称标识,因为一个通知可能对应多个类,所以这里我们value使用一个数组,该数组中的每个变量又是一个字典,其中的内容就是上边所说的三个参数:


// 添加通知中心
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject {
    // 以通知名为key来设置value并保存在通知中心
    // 从通知中心获取该通知名的所有注册信息
    NSMutableArray *array = self.classDictionary[aName];
    // 如果通知中心没有储存过该通知名的信息,就新建
    if (!array) {
        array = [NSMutableArray array];
    }
    // 向数组中添加传递过来的信息
    [array addObject:@{@"class": observer, @"selector": NSStringFromSelector(aSelector), @"object": anObject ? : [NSNull null]}];
    // 将更新过的数组重新添加到通知中心
    [self.classDictionary setObject:array forKey:aName];
}

发送通知的逻辑,其实就是通过发送过来的通知名标识aName在classDictionary属性中查找对应的通知信息,然后依次使用objc_msgSend发送消息,从而达到传值的目的,这里我们用系统封装好的NSInvocation类进行调用:


// 发送通知
- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject {
    [self postNotificationName:aName object:anObject userInfo:nil];
}

- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo {
    // 通过发送通知的通知名,找到在通知中心保存的该通知名的所有注册的类的信息
    NSMutableArray *array = self.classDictionary[aName];
    // 通过获取到的信息中的方法名和类信息,来逐一使用msgSend发送消息给目标类
    for (NSDictionary *mapDictionary in array) {
        // 当mapDictionary中的object与anObject一致或者接收者为null时,才调用方法,确保信息无误不会发错
        if ([mapDictionary[@"object"] isEqual:anObject] || [mapDictionary[@"object"] isKindOfClass:[NSNull class]]) {
            // NSInvocation;用来包装方法和对应的对象,它可以存储方法的名称,对应的对象,对应的参数,
            /*
             NSMethodSignature:签名:再创建NSMethodSignature的时候,必须传递一个签名对象,签名对象的作用:用于获取参数的个数和方法的返回值
             */
            // 创建签名对象的时候不是使用NSMethodSignature这个类创建,而是方法属于谁就用谁来创建,创建当前访问信息的class类中的selector方法的签名
            NSMethodSignature *signature = [[mapDictionary[@"class"] class] instanceMethodSignatureForSelector:NSSelectorFromString(mapDictionary[@"selector"])];
            
            // 1、通过创建的方法签名,创建NSInvocation对象
            NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
            // 给NSInvocation对象设置实现该selector方法的类信息
            invocation.target = mapDictionary[@"class"];
            // 给NSInvocation对象设置调用的方法的信息
            invocation.selector = NSSelectorFromString(mapDictionary[@"selector"]);
            // 包装要使用通知传递的信息,通过消息转发机制来实现跨界面传值
            // 注意:设置参数的索引时不能从0开始,因为0已经被self占用,1已经被_cmd占用
            ZJQNotification *notification = [[ZJQNotification alloc] initWithName:aName object:anObject userInfo:aUserInfo];
            [invocation setArgument:&notification atIndex:2];
            /* 第一个参数:需要给指定方法传递的值
                   第一个参数需要接收一个指针,也就是传递值的时候需要传递地址 */
            // 第二个参数:需要给指定方法的第几个参数传值
            
            // 2、调用NSInvocation对象的invoke方法
            // 只要调用invocation的invoke方法,就代表需要执行NSInvocation对象中制定对象的指定方法,并且传递指定的参数
            [invocation invoke];
        }
    }
}

移除通知时,通过给定的通知的信息,在classDictionary中删除对应的数据即可:


// 移除通知
// 找到对应的类,在classDictionary中删除即可
// 全部移除
- (void)removeObserver:(id)observer {
    // 创建一个临时字典,存放删除完了的数据,最后用这个字典更新classDictionary数据
    NSMutableDictionary *tempDictionary = [NSMutableDictionary dictionary];
    // 枚举遍历
    [self.classDictionary enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        NSMutableArray *tempArray = [obj mutableCopy];
        for (NSDictionary * mapDictionary in obj) {
            // 判断该类是不是observer的类,是就删除
            if ([mapDictionary[@"class"] isKindOfClass:[observer class]]) {
                [tempArray removeObject:mapDictionary];
            }
        }
        // 删除完了,添加到tempDictionary中
        [tempDictionary setObject:tempArray forKey:key];
    }];
    // 更新classDictionary数据
    self.classDictionary = tempDictionary;
}

// 根据通知名移除
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject {
    // 获取该通知名注册的所有信息
    NSMutableArray *array = self.classDictionary[aName];
    NSMutableArray *tempArray = [array mutableCopy];
    for (NSDictionary *mapDictionary in array) {
        // 判断该类是不是observer的类
        if ([mapDictionary[@"class"] isKindOfClass:[observer class]]) {
            // 如果该通知的接收方相等或者接收方不存在,再删除,确保不会误删
            if ([mapDictionary[@"object"] isEqual:anObject] || !anObject) {
                [tempArray removeObject:mapDictionary];
            }
        }
    }
    // 更新classDictionary数据
    [self.classDictionary setValue:tempArray forKey:aName];
}

通知原理

通知机制的核心是一个与线程关联的单例对象叫通知中心(NSNotificationCenter)。通知中心发送通知给观察者是同步的,也可以同通知队列(NSNotificationQueue)异步发送通知。
因为苹果的通知源码没有开源,所以我们看看GNUStep的源码。

属于结构
单例类

从我们之前使用通知的流程和代码来看,通知其实就是一个单例,方便随时访问。


static NSNotificationCenter *default_center = nil;

+ (NSNotificationCenter*) defaultCenter
{
  return default_center;
}

NSNotificationCenter : 消息中心

这个单例类中主要定义了两个表,一个存储所有注册通知信息的表的结构体,一个保存单个注册信息的节点结构体。


typedef struct NCTbl {
  Observation       *wildcard;  // 添加观察者时既没有传入 NotificationName ,又没有传入object,就会加在这个链表上,它里边的观察者可以接收所有的系统通知
  GSIMapTable       nameless;   // 添加观察者时没有传入 NotificationName 的表
  GSIMapTable       named;      // 添加观察者时传入了 NotificationName 的表
} NCTable

保存了观察者的信息:


typedef struct Obs {
  id        observer;   // 观察者对象
  SEL       selector;   // 方法信息
  struct Obs    *next;      // 指向下一个节点
  int       retained;   /* Retain count for structure.  */
  struct NCTbl  *link;      /* Pointer back to chunk table  */
} Observation;

以及一些宏定义:


#define        TABLE                ((NCTable*)_table)
#define        WILDCARD             (TABLE->wildcard)
#define        NAMELESS             (TABLE->nameless)
#define        NAMED                (TABLE->named)
#define        LOCKCOUNT            (TABLE->lockCount)

named表

在 named 表中,NotifcationName 作为表的 key,因为我们在注册观察者的时候是可以传入一个参数 object 用于只监听指定该对象发出的通知,并且一个通知可以添加多个观察者,所以还需要一张表来保存 object 和 Observer 的对应关系。这张表的 key、Value 分别是以 object 为 Key,Observer 为 value。用了链表这种数据结构实现保存多个观察者的情况。

在这里插入图片描述

在实际开发过程中 object 参数我们经常传 nil,这时候系统会根据 nil 自动生成一个 key,相当于这个 key 对应的 value(链表)保存的就是当前通知传入了 NotificationName 没有传入 object 的所有观察者。

nameless表

上边说了 named 表,那么 nameless 表就不难想象了,他注册时没有 NotificationName ,即没有了最外边一层键值对的约束了,其中就只有 object 和 Observation 所对应的键值对结构了:

在这里插入图片描述

wildcard表

这个表既没有 NotificationName 也没有 object 了,所以他就会在 nameless基础上在脱去一层键值对,那么它就只剩下一个链表了,该练表存储了可以接收所有通知的类的信息

在这里插入图片描述

添加观察者

使用方法addObserver:selector:name:object添加观察者,根据 GNUStep 的源码分析:


- (void) addObserver: (id)observer
            selector: (SEL)selector
                name: (NSString*)name
              object: (id)object
{
  Observation        *list;
  Observation        *o;
  GSIMapTable        m;
  GSIMapNode        n;
// observer为空时的报错
  if (observer == nil)
    [NSException raise: NSInvalidArgumentException
                format: @"Nil observer passed to addObserver ..."];
// selector为空时的报错
  if (selector == 0)
    [NSException raise: NSInvalidArgumentException
                format: @"Null selector passed to addObserver ..."];
// observer不能响应selector时的报错
  if ([observer respondsToSelector: selector] == NO)
    {
      [NSException raise: NSInvalidArgumentException
        format: @"[%@-%@] Observer '%@' does not respond to selector '%@'",
        NSStringFromClass([self class]), NSStringFromSelector(_cmd),
        observer, NSStringFromSelector(selector)];
    }
// 给表上锁
  lockNCTable(TABLE);
// 建立一个新Observation,存储这次注册的信息
  o = obsNew(TABLE, selector, observer);
  // 如果有name
  if (name) {
      // 在named表中 以name为key寻找value
      n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
      // named表中没有找到对应的value
      if (n == 0) {
          // 新建一个表
          m = mapNew(TABLE);
          // 由于这是对给定名称的首次观察,因此我们对该名称进行了复制,以便在map中无法对其进行更改(来自GNUStep的注释)
          name = [name copyWithZone: NSDefaultMallocZone()];
          // 新建表作为name的value添加在named表中
          GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
          GS_CONSUMED(name)
      } else { //named表中有对应的value
      	  // 取出对应的value
          m = (GSIMapTable)n->value.ptr;
      }
      // 将observation添加到正确object的列表中
      // 获取添加完后name对应的value的object对应的链表
      n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
      // n是object的value
      if (n == 0) { // 如果object对应value没有数据
          o->next = ENDOBS;
          // 将o作为object的value链表的头结点插入
          GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);
      } else { // 如果有object对应的value那么就直接添加到原练表的尾部
          // 在链表尾部加入o
          list = (Observation*)n->value.ptr;
          o->next = list->next;
          list->next = o;
      }
      // 这个else if 就是没有name有object的Observation,对object进行的操作相同,
  } else if (object) {
  	  // 直接获取object对应的value链表
      n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
      if (n == 0) { // 这个对应链表如果没有数据
          o->next = ENDOBS;
          // 将该observation作为头节点插入
          GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);
      } else { // 有数据,将obsevation直接插在原链表的后面
          list = (Observation*)n->value.ptr;
          o->next = list->next;
          list->next = o;
      }
 } else {
 	  // 既没有name又没有object,就加在WILDCARD链表中
      o->next = WILDCARD;
      WILDCARD = o;
 }
  // 解锁
  unlockNCTable(TABLE);
}

流程总结:
    1. 如果提供了name参数
      • 在named表中根据name查找对应的GSIMapTable。如果没有找到,则创建一个新的GSIMapTable并将其添加到NAMED表中。
      • 将新创建的Observation对象添加到GSIMapTable中object对应的链表的末尾
    1. 如果没有提供name参数,但提供了object参数:
      • 在nameless表中查找object对应的链表。如果没有找到,则将新创建的Observation对象作为头节点插入到nameless表中
      • 如果找到了object对应的链表,则将新创建的Obervation对象添加到链表的末尾。
    1. 如果既没有提供name也没有提供object,则将新创建的Observation对象添加到wildcard链表的头部
发送通知

使用方法postNotification:postNotificationName:object:userInfo或者postNotificationName:object:发送通知,后者默认userInfo为nil,同样使用GNUStep源码进行分析:

- (void) postNotification: (NSNotification*)notification {
  if (notification == nil) {
      [NSException raise: NSInvalidArgumentException
                  format: @"Tried to post a nil notification."];
    }
  [self _postAndRelease: RETAIN(notification)];
}

- (void) postNotificationName: (NSString*)name
                       object: (id)object {
  [self postNotificationName: name object: object userInfo: nil];
}


- (void) postNotificationName: (NSString*)name
                       object: (id)object
                     userInfo: (NSDictionary*)info {
  GSNotification        *notification;

  notification = (id)NSAllocateObject(concrete, 0, NSDefaultMallocZone());
  notification->_name = [name copyWithZone: [self zone]];
  notification->_object = [object retain];
  notification->_info = [info retain];
  [self _postAndRelease: notification];
}

我们发现,经典的源码书写,最终都只会调用 _postAndRelease:方法。不同的是,postNotification:方法外部直接传了一个NSNotification对象,其他两个方法都是内部进行了处理包装 成为了一个NSNotification对象,我们再看看_postAndRelease:方法做了什么:


- (void) _postAndRelease: (NSNotification*)notification {
  Observation        *o;
  unsigned        count;
  NSString        *name = [notification name];
  id                object;
  GSIMapNode        n;
  GSIMapTable        m;
  GSIArrayItem        i[64];
  GSIArray_t        b;
  GSIArray        a = &b;
   // name为空的报错,注册时可以注册无名,注册无名就等于说是所有的通知都能接收,但是发送通知时不可以
  if (name == nil) {
      RELEASE(notification);
      [NSException raise: NSInvalidArgumentException
                  format: @"Tried to post a notification with no name."];
    }
  object = [notification object];

  GSIArrayInitWithZoneAndStaticCapacity(a, _zone, 64, i);
  lockNCTable(TABLE);
  // 查找所有未指定name或object的观察者,加在a数组中,即将wildcard表中的数据都加在新建链表中
  for (o = WILDCARD = purgeCollected(WILDCARD); o != ENDOBS; o = o->next)
    {
      GSIArrayAddItem(a, (GSIArrayItem)o);
    }
  // 查找与通知的object相同但是没有name的观察者,加在a数组中
  if (object) {
  	  // 在nameless中找object对应的数据节点
      n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
      if (n != 0) { // 将其加入到新建链表中
          o = purgeCollectedFromMapNode(NAMELESS, n);
          while (o != ENDOBS) {
              GSIArrayAddItem(a, (GSIArrayItem)o);
              o = o->next;
            }
        }
    }

  // 查找name的观察者,但观察者的非零对象与通知的object不匹配时除外,加在a数组中
  if (name) {
  	  // 先匹配name
      n = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
      if (n) { // m指向name匹配到的数据
          m = (GSIMapTable)n->value.ptr;
      } else {
          m = 0;
      }
      if (m != 0) { // 如果上述name查找到了数据
          // 首先,查找与通知的object相同的观察者
          n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
          if (n != 0) { // 找到了与通知的object相同的观察者,就加入到新建链表中
              o = purgeCollectedFromMapNode(m, n);
              while (o != ENDOBS) {
                  GSIArrayAddItem(a, (GSIArrayItem)o);
                  o = o->next;
                }
            }

          if (object != nil) {
          // 接着是没有object的观察者,都加在新建链表中
              n = GSIMapNodeForSimpleKey(m, (GSIMapKey)nil);
              if (n != 0) { // 如果没有object并且有数据,就把其中的数据加到新建链表中
                  o = purgeCollectedFromMapNode(m, n);
                  while (o != ENDOBS) {
                      GSIArrayAddItem(a, (GSIArrayItem)o);
                      o = o->next;
                    }
                }
            }
        }
    }
  unlockNCTable(TABLE);

  // 发送通知,给之前新建链表中的所有数据
  count = GSIArrayCount(a);
  while (count-- > 0) {
      o = GSIArrayItemAtIndex(a, count).ext;
      if (o->next != 0) {
          NS_DURING {
              // 给observer发送selector,让其处理
              [o->observer performSelector: o->selector
                                withObject: notification];
            }
          NS_HANDLER {
              BOOL        logged;
              // 尝试将通知与异常一起报告,但是如果通知本身有问题,我们只记录异常。
              NS_DURING
                NSLog(@"Problem posting %@: %@", notification, localException);
                logged = YES;
              NS_HANDLER
                logged = NO;
              NS_ENDHANDLER
                if (NO == logged)
                { 
                  NSLog(@"Problem posting notification: %@", localException);
                }  
            }
          NS_ENDHANDLER
        }
    }
  lockNCTable(TABLE);
  GSIArrayEmpty(a);
  unlockNCTable(TABLE);

  RELEASE(notification);
}

流程总结
    1. 首先会创建一个数组,observerArray用来保存需要通知的observer。
    1. 遍历wildcard链表,将observer添加到observerArray数组中
    1. 若存在object,在nameless table中找到以object为key到链表,然后遍历找到的链表,将observer添加到observerArray数组中
    1. 若存在NotificationName,在named table中以NotificationName为key找到对应的table,然后再在找到的他变了中以object为key找到对应的链表,遍历链表,将observer添加到observerArray数组中。结果object不为nil,则以nil为key找到对应的链表,遍历链表,将observer添加到observerArray数组中。
    1. 至此所有关于当前通知的observer(wildcard + nameless + named)都已经加入到了数组observerArray中。遍历observerArray数组,取出其中的observer节点(包含了观察者对象和selector)其中调用观察者的方法,调用形式如下:

[o->observer performSelector: o->selector withObject: notification];

移除通知

- (void) removeObserver: (id)observer {
  if (observer == nil)
    return;

  [self removeObserver: observer name: nil object: nil];
}

- (void) removeObserver: (id)observer
                   name: (NSString*)name
                 object: (id)object {
  // 当其要移除的信息都为空时,直接返回
  if (name == nil && object == nil && observer == nil)
      return;

  lockNCTable(TABLE);
  // name和object都为nil,就在wildcard链表里删除对应observer的注册信息
  if (name == nil && object == nil) {
      WILDCARD = listPurge(WILDCARD, observer);
    }
  // name为空时
  if (name == nil) {
      GSIMapEnumerator_t        e0;
      GSIMapNode                n0;
      // 首先尝试删除为此object对应的所有命名项目
      // 在named表中
      e0 = GSIMapEnumeratorForMap(NAMED);
      n0 = GSIMapEnumeratorNextNode(&e0);
      while (n0 != 0) {
          GSIMapTable                m = (GSIMapTable)n0->value.ptr;
          NSString                *thisName = (NSString*)n0->key.obj;

          n0 = GSIMapEnumeratorNextNode(&e0);
          if (object == nil) { // 如果object为空,直接清除named表
          	  // 清空named表
              GSIMapEnumerator_t        e1 = GSIMapEnumeratorForMap(m);
              GSIMapNode                n1 = GSIMapEnumeratorNextNode(&e1);

              while (n1 != 0) {
                  GSIMapNode        next = GSIMapEnumeratorNextNode(&e1);

                  purgeMapNode(m, n1, observer);
                  n1 = next;
                }
          } else {
          // 以object为key找到对应链表,清空该链表
              GSIMapNode        n1;
              n1 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
              if (n1 != 0) {
                  purgeMapNode(m, n1, observer);
                }
            }
          if (m->nodeCount == 0) {
              mapFree(TABLE, m);
              GSIMapRemoveKey(NAMED, (GSIMapKey)(id)thisName);
            }
        }
      // 开始操作nameless表
      if (object == nil) { // object为空时
      	  // 清空nameless表
          e0 = GSIMapEnumeratorForMap(NAMELESS);
          n0 = GSIMapEnumeratorNextNode(&e0);
          while (n0 != 0) {
              GSIMapNode        next = GSIMapEnumeratorNextNode(&e0);

              purgeMapNode(NAMELESS, n0, observer);
              n0 = next;
            }
        } else { // object不为空
          // 找到对应的observer链表,清空该链表
          n0 = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
          if (n0 != 0) {
              purgeMapNode(NAMELESS, n0, observer);
            }
        }
   } else { // name不为空
      GSIMapTable                m;
      GSIMapEnumerator_t        e0;
      GSIMapNode                n0;

      n0 = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
      // 如果没有和这个name相同的key,直接返回
      if (n0 == 0) {
          unlockNCTable(TABLE);
          return;                /* Nothing to do.        */
      }
      m = (GSIMapTable)n0->value.ptr; // 找到name作为key对应的数据信息

      if (object == nil) {
      // 如果object为nil,就清空刚才找到的name对应的数据信息
          e0 = GSIMapEnumeratorForMap(m);
          n0 = GSIMapEnumeratorNextNode(&e0);

          while (n0 != 0) {
              GSIMapNode        next = GSIMapEnumeratorNextNode(&e0);

              purgeMapNode(m, n0, observer);
              n0 = next;
            }
      } else {
      // 如果object不为空,清空object对应的链表
          n0 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
          if (n0 != 0) {
              purgeMapNode(m, n0, observer);
            }
        }
      // 因为其中的数据清除完了,所以记得清除named表中的作为key的name
      if (m->nodeCount == 0) {
          mapFree(TABLE, m);
          GSIMapRemoveKey(NAMED, (GSIMapKey)((id)name));
        }
    }
  unlockNCTable(TABLE);
}

流程总结
    1. 若NotificationName和object都为nil,则清空wildcard链表。
    1. 若NotificationName为nil,遍历named table,若object为nil,则清空named table,若object不为nil,则以object为key找到对应的链表,然后清空链表。在nameless table中以object为key找到对应的observer链表,然后清空,若object也为nil,则清空nameless table
    1. 若NotificationName不为nil,在name table中以NotificationName为key找到对应的table,若object为nil,则清空找到的table,若object不为nil,则以object为key在找到的table中取出对应的链表,然后清空链表

面试题

1. 通知的发送是同步的还是异步的?发送消息与接收消息的线程是同一个线程吗?

答:
通知中心发送通知给观察者是同步的,也可以同通知队列(NSNotificationQueue)异步发送通知。

在抛出通知以后,观察者在通知事件处理完成以后(可以通过休眠3秒来测试),抛出者才会往下继续执行,也就是说这个过程默认是同步的;当发送通知时,通知中心会一直等到所有的observer都收到并且处理了通知才会返回到poster。

接收通知的线程,和发送通知所处的线程是同一个线程。也就是说如果要在接收通知的时候更新UI,需要注意发送通知的线程是否为主线程。

2. 如何使用异步发送通知?

  1. 让通知事件处理方法在子线程中执行:

- (void)viewDidLoad {
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"NotificationName" object:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"--current thread: %@", [NSThread currentThread]);
    NSLog(@"Begin post notification");
    [[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationName" object:nil];
    NSLog(@"End");
}

- (void)test {
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{    // 异步执行 + 串行队列
        NSLog(@"--current thread: %@", [NSThread currentThread]);
        NSLog(@"Handle notification and sleep 3s");
        sleep(3);
    });
}

  1. 可以通过 NSNotificationQueueenqueueNotification: postingStyle: enqueueNotification: postingStyle: coalesceMask: forModes: 方法将通告放入队列,实现异步发送,在把通告放入队列之后,这些方法会立即将控制权返回给调用对象。

- (void)viewDidLoad {
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"NotificationName" object:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"--current thread: %@", [NSThread currentThread]);
    NSLog(@"Begin post notification");
    NSNotification *notification = [NSNotification notificationWithName:@"NotificationName" object:nil];
    [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];
    NSLog(@"End");
}

- (void)test {
    NSLog(@"--current thread: %@", [NSThread currentThread]);
    NSLog(@"Handle notification and sleep 3s");
    sleep(3);
}

3. NSNotificationQueue和runloop的关系?

postingStyle参数就是定义通知调用和runloop状态之间的关系。
该参数的三个可选参数:

    1. NSPostWhenIdle : 通知回调方法是等待到当下线程runloop进入等待状态才会调用。( 表示在当前 runloop 空闲时分发通知)
    1. NSPostASAP:通知回调方法是等待到当下线程runloop开始接收事件源的时候就会调用。(表示尽快分发通知,通常是在当前 runloop 中立即分发。)
    1. NSPostNow:其实和直接用默认的通知中心添加通知是一样的,通知马上调用回调方法。(这种模式下,通知会立即被分发,不需要等待 runloop 的任何事件触发。)

4. 页面销毁时不移除通知会崩溃吗?

  • 在观察者对象释放之前,需要调用removeOberver方法将观察者从通知中心移除,否则程序可能会出现崩溃。但从 iOS9 开始,即使不移除观察者对象,程序也不会出现异常。
  • 这是因为在iOS9以后,通知中心持有的观察者由unsafe_unretained引用变为weak引用。即使不对观察者手动移除,持有的观察者的引用也会在观察者被回收后自动置空。但是通过addObserverForName:object:queue:usingBlock:方法注册的观察者需要手动释放,因为通知中心持有的是它们的强引用。

5. 多次添加同一个通知会是什么结果?多次移除通知呢?

  • 多次添加同一个通知,每次添加都会成功注册一个新的观察者,通知发送时观察者方法会调用多次
  • 多次移除,是安全的。第一次调用会成功移除观察者。后续的移除调用则会什么也不做,因为观察者已经不存在了。

6. 下面的方法会不会接收到通知?

不会,上文介绍NSNotificationCenter时介绍了center的结构。

  • 注册通知在添加observer时,路径为TestNotification -> @1 -> self
  • 发送通知在查找observer时,路径为TestNotification -> nil -> observer list

7. object是干嘛的?是不是可以用来传值?

object是用来过滤Notification的,只接收指定的sender所发的Notification,传值请用userInfo,而不是object。

  • 30
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值