假期学习-- iOS 通知详解

打印 上一主题 下一主题

主题 681|帖子 681|积分 2043

iOS 通知详解

数据结构

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

这个单例类中主要定义了两个表,一个存储所有注册通知信息的表的结构体,一个生存单个注册信息的节点结构体。
  1. typedef struct NCTbl {
  2.   Observation       *wildcard;  // 添加观察者时既没有传入 NotificationName ,又没有传入object,就会加在这个链表上,它里边的观察者可以接收所有的系统通知
  3.   GSIMapTable       nameless;   // 添加观察者时没有传入 NotificationName 的表
  4.   GSIMapTable       named;      // 添加观察者时传入了 NotificationName 的表
  5. } NCTable
复制代码
观察者信息的结构:
  1. typedef struct  Obs {
  2.   id        observer;   // 观察者对象
  3.   SEL       selector;   // 方法信息
  4.   struct Obs    *next;      // 指向下一个节点
  5.   int       retained;   /* Retain count for structure.  */
  6.   struct NCTbl  *link;      /* Pointer back to chunk table  */
  7. } Observation;
复制代码
named表

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

在实际开发过程中 object 参数我们常常传 nil,这时间体系会根据 nil 自动天生一个 key,相当于这个 key 对应的 value(链表)生存的就是当前通知传入了 NotificationName 没有传入 object 的所有观察者。
同理nameless表和wildcard表如下:


添加观察者

使用方法addObserver:selector:namebject添加观察者,根据 GNUStep 的源码分析:
  1. - (void) addObserver: (id)observer
  2.             selector: (SEL)selector
  3.                 name: (NSString*)name
  4.               object: (id)object
  5. {
  6.   Observation        *list;
  7.   Observation        *o;
  8.   GSIMapTable        m;
  9.   GSIMapNode        n;
  10. // observer为空时的报错
  11.   if (observer == nil)
  12.     [NSException raise: NSInvalidArgumentException
  13.                 format: @"Nil observer passed to addObserver ..."];
  14. // selector为空时的报错
  15.   if (selector == 0)
  16.     [NSException raise: NSInvalidArgumentException
  17.                 format: @"Null selector passed to addObserver ..."];
  18. // observer不能响应selector时的报错
  19.   if ([observer respondsToSelector: selector] == NO)
  20.     {
  21.       [NSException raise: NSInvalidArgumentException
  22.         format: @"[%@-%@] Observer '%@' does not respond to selector '%@'",
  23.         NSStringFromClass([self class]), NSStringFromSelector(_cmd),
  24.         observer, NSStringFromSelector(selector)];
  25.     }
  26. // 给表上锁
  27.   lockNCTable(TABLE);
  28. // 建立一个新Observation,存储这次注册的信息
  29.   o = obsNew(TABLE, selector, observer);
  30.   // 如果有name
  31.   if (name) {
  32.       // 在named表中 以name为key寻找value
  33.       n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
  34.       // named表中没有找到对应的value
  35.       if (n == 0) {
  36.           // 新建一个表
  37.           m = mapNew(TABLE);
  38.           // 由于这是对给定名称的首次观察,因此我们对该名称进行了复制,以便在map中无法对其进行更改(来自GNUStep的注释)
  39.           name = [name copyWithZone: NSDefaultMallocZone()];
  40.           // 新建表作为name的value添加在named表中
  41.           GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
  42.           GS_CONSUMED(name)
  43.       } else { //named表中有对应的value
  44.                 // 取出对应的value
  45.           m = (GSIMapTable)n->value.ptr;
  46.       }
  47.       // 将observation添加到正确object的列表中
  48.       // 获取添加完后name对应的value的object对应的链表
  49.       n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
  50.       // n是object的value
  51.       if (n == 0) { // 如果object对应value没有数据
  52.           o->next = ENDOBS;
  53.           // 将o作为object的value链表的头结点插入
  54.           GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);
  55.       } else { // 如果有object对应的value那么就直接添加到原练表的尾部
  56.           // 在链表尾部加入o
  57.           list = (Observation*)n->value.ptr;
  58.           o->next = list->next;
  59.           list->next = o;
  60.       }
  61.       // 这个else if 就是没有name有object的Observation,对object进行的操作相同,
  62.   } else if (object) {
  63.             // 直接获取object对应的value链表
  64.       n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
  65.       if (n == 0) { // 这个对应链表如果没有数据
  66.           o->next = ENDOBS;
  67.           // 将该observation作为头节点插入
  68.           GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);
  69.       } else { // 有数据,将obsevation直接插在原链表的后面
  70.           list = (Observation*)n->value.ptr;
  71.           o->next = list->next;
  72.           list->next = o;
  73.       }
  74. } else {
  75.           // 既没有name又没有object,就加在WILDCARD链表中
  76.       o->next = WILDCARD;
  77.       WILDCARD = o;
  78. }
  79.   // 解锁
  80.   unlockNCTable(TABLE);
  81. }
复制代码
流程

1.起首查抄添加观察者方法的参数是否精确 ;
2.创建新的Observation存储这次注册的信息
3.先判断name是否存在,存在就把name作为key去named table查找value;没找到就新建表和对应的name作为key-value存入named table中;找到就获取value(object-observation的表) 然后这时在这个表中查找object ;没找到新建object-observation插入表中;找到先取出对应的observation链表,在链表尾部插入 ;
4.然后判断object是否存在,其他都和上面差不多,只不过这次是从nameless table中开始 ;
5.若既没有 NotificationName 也没有 object,那么就加在 WILDCARD 链表中。
发送通知

使用方法postNotification:, postNotificationNamebject:userInfo或者postNotificationNamebject:发送通知,后者默认userInfo为nil,同样使用GNUStep源码进行分析:
  1. - (void) postNotification: (NSNotification*)notification {
  2.   if (notification == nil) {
  3.       [NSException raise: NSInvalidArgumentException
  4.                   format: @"Tried to post a nil notification."];
  5.     }
  6.   [self _postAndRelease: RETAIN(notification)];
  7. }
  8. - (void) postNotificationName: (NSString*)name
  9.                        object: (id)object {
  10.   [self postNotificationName: name object: object userInfo: nil];
  11. }
  12. - (void) postNotificationName: (NSString*)name
  13.                        object: (id)object
  14.                      userInfo: (NSDictionary*)info {
  15.   GSNotification        *notification;
  16.   notification = (id)NSAllocateObject(concrete, 0, NSDefaultMallocZone());
  17.   notification->_name = [name copyWithZone: [self zone]];
  18.   notification->_object = [object retain];
  19.   notification->_info = [info retain];
  20.   [self _postAndRelease: notification];
  21. }
复制代码
最终都只会调用_postAndRelease:方法。
  1. - (void) _postAndRelease: (NSNotification*)notification {
  2.   Observation        *o;
  3.   unsigned        count;
  4.   NSString        *name = [notification name];
  5.   id                object;
  6.   GSIMapNode        n;
  7.   GSIMapTable        m;
  8.   GSIArrayItem        i[64];
  9.   GSIArray_t        b;
  10.   GSIArray        a = &b;
  11.    // name为空的报错,注册时可以注册无名,注册无名就等于说是所有的通知都能接收,但是发送通知时不可以
  12.   if (name == nil) {
  13.       RELEASE(notification);
  14.       [NSException raise: NSInvalidArgumentException
  15.                   format: @"Tried to post a notification with no name."];
  16.     }
  17.   object = [notification object];
  18.   GSIArrayInitWithZoneAndStaticCapacity(a, _zone, 64, i);
  19.   lockNCTable(TABLE);
  20.   // 查找所有未指定name或object的观察者,加在a数组中,即将wildcard表中的数据都加在新建链表中
  21.   for (o = WILDCARD = purgeCollected(WILDCARD); o != ENDOBS; o = o->next)
  22.     {
  23.       GSIArrayAddItem(a, (GSIArrayItem)o);
  24.     }
  25.   // 查找与通知的object相同但是没有name的观察者,加在a数组中
  26.   if (object) {
  27.             // 在nameless中找object对应的数据节点
  28.       n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
  29.       if (n != 0) { // 将其加入到新建链表中
  30.           o = purgeCollectedFromMapNode(NAMELESS, n);
  31.           while (o != ENDOBS) {
  32.               GSIArrayAddItem(a, (GSIArrayItem)o);
  33.               o = o->next;
  34.             }
  35.         }
  36.     }
  37.   // 查找name的观察者,但观察者的非零对象与通知的object不匹配时除外,加在a数组中
  38.   if (name) {
  39.             // 先匹配name
  40.       n = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
  41.       if (n) { // m指向name匹配到的数据
  42.           m = (GSIMapTable)n->value.ptr;
  43.       } else {
  44.           m = 0;
  45.       }
  46.       if (m != 0) { // 如果上述name查找到了数据
  47.           // 首先,查找与通知的object相同的观察者
  48.           n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
  49.           if (n != 0) { // 找到了与通知的object相同的观察者,就加入到新建链表中
  50.               o = purgeCollectedFromMapNode(m, n);
  51.               while (o != ENDOBS) {
  52.                   GSIArrayAddItem(a, (GSIArrayItem)o);
  53.                   o = o->next;
  54.                 }
  55.             }
  56.           if (object != nil) {
  57.           // 接着是没有object的观察者,都加在新建链表中
  58.               n = GSIMapNodeForSimpleKey(m, (GSIMapKey)nil);
  59.               if (n != 0) { // 如果没有object并且有数据,就把其中的数据加到新建链表中
  60.                   o = purgeCollectedFromMapNode(m, n);
  61.                   while (o != ENDOBS) {
  62.                       GSIArrayAddItem(a, (GSIArrayItem)o);
  63.                       o = o->next;
  64.                     }
  65.                 }
  66.             }
  67.         }
  68.     }
  69.   unlockNCTable(TABLE);
  70.   // 发送通知,给之前新建链表中的所有数据
  71.   count = GSIArrayCount(a);
  72.   while (count-- > 0) {
  73.       o = GSIArrayItemAtIndex(a, count).ext;
  74.       if (o->next != 0) {
  75.           NS_DURING {
  76.               // 给observer发送selector,让其处理
  77.               [o->observer performSelector: o->selector
  78.                                 withObject: notification];
  79.             }
  80.           NS_HANDLER {
  81.               BOOL        logged;
  82.               // 尝试将通知与异常一起报告,但是如果通知本身有问题,我们只记录异常。
  83.               NS_DURING
  84.                 NSLog(@"Problem posting %@: %@", notification, localException);
  85.                 logged = YES;
  86.               NS_HANDLER
  87.                 logged = NO;
  88.               NS_ENDHANDLER
  89.                 if (NO == logged)
  90.                 {
  91.                   NSLog(@"Problem posting notification: %@", localException);
  92.                 }  
  93.             }
  94.           NS_ENDHANDLER
  95.         }
  96.     }
  97.   lockNCTable(TABLE);
  98.   GSIArrayEmpty(a);
  99.   unlockNCTable(TABLE);
  100.   RELEASE(notification);
  101. }
复制代码
流程

简单的说就是查找到对应的根据通知的参数查找到对应的observation(不需要添加插入),然后按顺序存到链表中,找完之后按顺序遍历实行 ;
1.起首查抄参数中的name是否存在 ,不存在报错 ;
2.起首把wildcard中可以吸取所有的observation存入链表中 (查找所有未指定name或object的观察者,加在a数组中,即将wildcard表中的数据都加在新建链表中)
3.然后在nameless table中查找与参数object(先判断obect为nil的情况,然后在判断object为nil的情况,两种情况用于查找的key不同)对应的observation链表,把其中的元素也遍历插入实行链表中 (查找与通知的object相同但是没有name的观察者,加在a数组中)
4.末了在name table中查找,同理,先找name,然后再从中找对应object的obsevation链表,把其中的元素也遍历插入实行链表中 (先匹配name,起首,查找与通知的object相同的观察者,接着是没有object的观察者,都加在新建链表中)
5.末了遍历实行链表中的observation,给observer发送selector,让其处置处罚


  • 注意:关于能不能查找到的问题,我们只需要知道它是从表外往表里找的就行了,下面会提到一个例子 ;
移除通知

不给出源码了:
流程

1.若 NotificationName 和 object 都为 nil,则清空 wildcard 链表。
2.若 NotificationName 为 nil,遍历 named table,若 object 为 nil,则清空 named table,若 object 不为 nil,则以 object 为 key 找到对应的链表,然后清空链表。在 nameless table 中以 object 为 key 找到对应的 observer 链表,然后清空,若 object 也为 nil,则清空 nameless table。
3.若 NotificationName 不为nil,在 named table 中以 NotificationName 为 key 找到对应的 table,若 object 为 nil,则清空找到的 table,若 object 不为 nil,则以 object 为 key 在找到的 table 中取出对应的链表,然后清空链表。
一些问题

下面的方法不会吸取到通知?

  1. // 添加观察
  2. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:@1];
  3. // 通知发送
  4. [NSNotificationCenter.defaultCenter postNotificationName:@"TestNotification" object:nil];
复制代码
这里我理解的就两点:


  • 注册是无论如何都可以注册的,而且也是从外往里注册的
  • 通知发送(查找)是先从wildcard到nameless table到name table的顺序查找的,而且都是从外往里找的;如许也能知道通知实行的顺序 ;
  • 或者是只要发送通知的参数和观察者的关系是前者包含后者,就可以找到
通知的发送时同步的,照旧异步的?发送消息与吸取消息的线程是同一个线程么?

通知中央发送通知给观察者是同步的,也可以用通知队列(NSNotificationQueue)异步发送通知。
而且要注意**吸取通知的线程,和发送通知所处的线程是同一个线程。**也就是说假如要在吸取通知的时间更新 UI,需要注意发送通知的线程是否为主线程。
如何使用异步发送通知?

1.让通知事件处置处罚方法的内部实现再次说明在子线程实现 :
  1. - (void)viewDidLoad {
  2.     [super viewDidLoad];
  3.     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"NotificationName" object:nil];
  4. }
  5. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  6.     NSLog(@"--current thread: %@", [NSThread currentThread]);
  7.     NSLog(@"Begin post notification");
  8.     [[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationName" object:nil];
  9.     NSLog(@"End");
  10. }
  11. //- (void)test {
  12. //   NSLog(@"--current thread: %@", [NSThread currentThread]);
  13. //    NSLog(@"Handle notification and sleep 3s");
  14. //    sleep(3);
  15. //}
  16. - (void)test {
  17.     dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
  18.     dispatch_async(queue, ^{    // 异步执行 + 串行队列
  19.         NSLog(@"--current thread: %@", [NSThread currentThread]);
  20.         NSLog(@"Handle notification and sleep 3s");
  21.         sleep(3);
  22.     });
  23. }
复制代码
2.可以通过 NSNotificationQueue 的 enqueueNotification: postingStyle: 和 enqueueNotification: postingStyle: coalesceMask: forModes: 方法将通告放入队列,实现异步发送,在把通告放入队列之后,这些方法会立即将控制权返回给调用对象。
这里的异步大概有点奇怪,我的理解是:这里的异步是指和发送通知这个使命异步,而不是队列中的通知异步发送 ;
好比:
  1. - (void)viewDidLoad {
  2.     [super viewDidLoad];
  3.     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"NotificationName" object:nil];
  4. }
  5. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  6.    
  7.     dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
  8.     dispatch_async(queue, ^{    // 异步执行 + 串行队列
  9.         NSLog(@"--current thread: %@", [NSThread currentThread]);
  10.         NSLog(@"Begin post notification");
  11.         [[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationName" object:nil];
  12.         NSLog(@"End");
  13.     });
  14. }
  15. - (void)test {
  16.     NSLog(@"--current thread: %@", [NSThread currentThread]);
  17.     NSLog(@"Handle notification and sleep 3s");
  18.     sleep(3);
  19. }
复制代码
这里[[NSNotificationCenter defaultCenter] postNotificationName“NotificationName” object:nil];在队列中实行时,会添加test的同步实利用命进队列,如许导致了要先等候test实行完毕才会解除壅闭完成[[NSNotificationCenter defaultCenter] postNotificationName“NotificationName” object:nil];的使命,末了才气实行NSLog(@“End”);
但假如使用通知队列:
  1. - (void)viewDidLoad {
  2.     [super viewDidLoad];
  3.     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"NotificationName" object:nil];
  4. }
  5. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  6.     NSLog(@"--current thread: %@", [NSThread currentThread]);
  7.     NSLog(@"Begin post notification");
  8.     NSNotification *notification = [NSNotification notificationWithName:@"NotificationName" object:nil];
  9.     [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];
  10.     NSLog(@"End");
  11. }
  12. - (void)test {
  13.     NSLog(@"--current thread: %@", [NSThread currentThread]);
  14.     NSLog(@"Handle notification and sleep 3s");
  15.     sleep(3);
  16. }
复制代码
这时实行[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];时,会把通知的使命到场通知队列,这时发送消息的使命已经完成了,于是就可以向主队列中同步添加NSLog(@“End”);,而通知队列中的通知时异步添加到串行队列队列中的,固然没有创建新的线程,使命的实行也是顺序实行的,但这也意味着这里的通知实行没有壅闭发送消息的使命,所以这里的通知实行和发送消息的使命是异步的 ;
页面销毁时不移除通知会崩溃吗?

在观察者对象释放之前,需要调用 removeOberver 方法将观察者从通知中央移除,否则程序大概会出现崩溃。**(因为这个时间大概出现野指针)**但从 iOS9 开始,即使不移除观察者对象,程序也不会出现异常。
这是因为在 iOS9 以后,通知中央持有的观察者由 unsafe_unretained 引用变为weak引用。即使不对观察者手动移除,持有的观察者的引用也会在观察者被采取后自动置空。但是通过 addObserverForNamebject: queue:usingBlock: 方法注册的观察者需要手动释放,因为通知中央持有的是它们的强引用。
多次添加同一个通知会是什么结果?多次移除通知呢?



  • 多次添加同一个通知,观察者方法会调用多次(可以看之前的源码,在注册观察者时不会对链表里面原来的判断,而是直接到场链表末了,比及发送通知在表中查找观察者时,只要找到就会实行) ;
  • 多次移除,没关系。
为什么注册通知时可以空名注册,但是发送通知时却不可以?

具体的原因欠好说,但实际出现这种征象的原因是因为在发送通知的方法里面最开始就有判段是否空名,注册就没有 ;
object是干嘛的?是不是可以用来传值?

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

数据人与超自然意识

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表