扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
NSNotification是iOS中一个调度消息通知的类,采用单例设计模式,在开发中实现传值、回调等。在iOS中,NSNotification是使用观察者模式来实现用于跨层传递消息。
创新互联公司主要从事成都网站建设、成都做网站、网页设计、企业做网站、公司建网站等业务。立足成都服务涪城,十余年网站建设经验,价格优惠、服务专业,欢迎来电咨询建站服务:18982081108
NSNotification包含了一些用于向其他对象发送通知的必要信息,包括名称、对象和可选字典,并由NSNotificationCenter或NSDistributedNotificationCenter的实例进行发送。name是标识通知的标记、object是保存发送通知的对象、userinfo存储其他相关对象。 这里主要注意的是:NSNotification对象是不可变的。
可以使用 notificationWithName:object: 或 notificationWithName:object:userInfo: 创建通知对象。但实际开发中,一般是直接使用NSNotificationCenter调用 postNotificationName:object: 或 postNotificationName:object:userInfo: ,这两个类方法会在内部直接创建NSNotification对象,并发出通知。
从官网文档可知,NSNotification是不能直接实例化的,如果用init方法进行实例化时,会引发异常。还有需要注意的是如果我们自己去实现构造方法时,不能在super上调用init方法。
NSNotificationCenter提供了一套机制来发送通知,每个运行中的应用程序都有一个defaultCenter通知中心,我们可以创建新的通知中心来组织特定上下文中的通信。 NSNotificationCenter暴露给外部的字段只有一个defaultCenter,并且该字段是只读的,暴露出来的方法分为三种:添加、移除通知观察者和发出通知。详细如下表所示:
postNotificationName:object:
postNotificationName:object:userInfo: |
相关说明:
简单理解为:通知中心的缓冲区。尽管通知中心已经分发通知,但放置到队列中的通知可能会延迟,直到runloop结束或者runloop空闲时才发送。如果有多个相同的通知,NSNotificationQueue会将其进行合并,以便在发布多个通知的情况下只发送一个通知。
通知队列按照先进先出(FIFO)的顺序维护通知。当一个通知移动到队列的前面时,队列将它发送到通知中心,然后再将通知分派给所有注册为观察者的对象。每个线程都有一个默认的通知队列,该队列与流程的默认通知中心相关联。我们也可以创建自己的通知队列。
和NSNotificationCenter一样,NSNotificationQueue也只暴露了一个字段:defaultQueue,返回当前线程的默认通知队列。方法分为:创建通知队列和管理通知。详细说明如下表所示:
dequeueNotificationsMatching:coalesceMask:
enqueueNotification:postingStyle:coalesceMask:forModes: |
方法相关说明:
在上面的方法中,需要注意的2个常量,相关说明如下:
NSNotificationCenter定义了两个Table,同时为了封装观察者信息,也定义了Observation保存观察者信息。他们的结构体可以简化如下所示:
在NSNotificationCenter内部一共保存了两张表,一张用于保存添加观察者的时候传入的NotificationName的情况;一张用于保存添加观察者的时候没有传入NotificationCenter的情况,详细分析如下:
在Named Table中,NotificationName作为表的key,因为我们在注册观察者的时候是可以传入一个object参数用于只监听该对象发出的通知,并且一个通知可以添加多个观察者,所以还需要一张表用来保存object和observe的对应关系。这张表的key、value分别是以object为key,observe为value。所以对于Named Table,最终的结构为:
Named Table
特别说明:在实际开发中,我们经常将object参数传nil,这个时候系统会根据nil自动产生一个key。相当于这个key对应的value(链表)保存的就是对于当前NotificationName没有传入object的所有观察者。当NotificationName被发送时,在链表中的观察者都会收到通知。
UNamed Table结构比Named Table简单得多。因为没有NotificationName作为key。这里直接就以object为key,比Named Table少了一层Table嵌套。
UnNamed Table
如果在注册观察者时没有传入NotificationName,同时没有传入object,所有的系统通知都会发送到注册的对象里。
首先在初始化NSNotificationCenter时会创建一个对象,这个对象里面保存了Named Table、UNamed Table和其他信息。
在没有传入NotificationName的情况和上面的过程类似,只不过是直接根据object去对应的链表而已。如果既没有传入NotificationName,也没有传入object,则这个观察者会添加到wildcard链表中。
发送通知一般是调用 postNotificationName:object:userInfo: 方法来实现。该方法内部会实例化一个NSNotification来保存传入的各种参数,包括name、object和userinfo。
发送通知的流程总体来说就是根据NotificationName查找到对应的Observer链表,然后遍历整个链表,给每个Observer结点中保存的对象及SEL,来向对象发送消息。具体流程如下:
这个方式也就能说明,发送通知的线程和接收通知的线程都是同一个线程。
NSNotification和线程同步之间是什么关系呢?先看下官方文档的说明:
翻译过来意思为:
更多关于NSNotification与线程之间的关系,请阅读下面的文章: iOS开发 - NSNotification和线程相关
总的来说,NSNotification的三个相关类的作用,可以用下图进行归纳总结。
总结
用于异步发送消息的通知队列,这个异步并不是开启线程,而是把通知存到双向链表实现的队列里面,等待某个时机触发。触发时调用 NSNotificationCenter 的发送接口进行发送通知,这么看 NSNotificationQueue 最终还是调用 NSNotificationCenter 进行消息的分发,另外 NSNotificationQueue 是依赖 runloop 的,所以如果线程的 runloop 未开启则无效。
把通知添加到队列等待发送,同时提供了一些附加条件供开发者选择,如:什么时候发送通知、如何合并通知等,系统给了如下定义。
可以,因为 notificationcenter 对观察者的引用是 weak ,当观察者释放的时候,观察者的指针值被置为 nil
会调用多次 observer 的 action 。多次移除没有任何影响。
NCTable 结构体中核心的三个变量: wildcard 、 named 、 nameless ,在源码中直接用宏定义表示了: WILDCARD 、 NAMELESS 、 NAMED 。
❶ NAMED 是个宏,表示名为 named 的字典。如果通知的 name 存在,则以 name 为 key 从 named 字典中取出值 n (这个 n 其实被 MapNode 包装了一层,便于理解这里直接认为没有包装),这个 n 还是个字典。
❷ n 不存在,则先取缓存,如果缓存没有则新建一个 map
❸ n 存在则把值取出来赋值给 m
❹ 然后以 object 为 key ,从字典中取出对应的值,这个值就是 Observation 类型的链表,然后把刚开始创建的 Observation 对象 o 存储进去。
❶ 以 object 为 key ,从 nameless 字典中取出对应的 value , value 是个链表结构。
❷ 不存在则新建链表,并存到 map 中
❸ 存在则把值接到链表的节点上
如果注册通知时传入 name ,那么会是一个双层的存储结构。首先找到 NCTable 中的 named 表,这个表存储了 name 的通知。接着以 name 作为 key ,找到 value ,这个 value 依然是一个 map 。最后, map 的结构是以 object 作为 key , Observation 对象为 value ,这个 Observation 对象的结构上面已经解释,主要存储了 observer SEL 。
以 object 为 key ,从 nameless 字典中取出 value ,此 value 是个 Observation 类型的链表。接着把创建的 Observation 类型的对象 o 存储到链表中。只存在 object 时存储只有一层,那就是 object 和 Observation 对象之间的映射。
这种情况直接把 Observation 对象存放在了 Observation *wildcard 链表结构中。
发送通知的核心逻辑比较简单,基本上就是查找和调用响应方法,从三个存储容器中: named 、 nameless 、 wildcard 去查找对应的 Observation 对象,然后通过 performSelector :逐一调用响应方法,这就完成了发送流程。
主要做了三件事:查找通知、发送、释放资源。
❶ 通过 name object 从 named 、 nameless 、 wildcard 表中查找对应的通知(保存了 observer 和 sel )。
❷ 执行发送,即调用 performSelector 执行响应方法,从这里可以看出是同步的。
❸ 释放 notification 对象。
因为查找时做了这个链表的遍历,所以删除时会把重复的通知全都删除掉
查找时仍然以 name 和 object 为准,再加上 observer 做区分。
上面介绍的 NSNotificationCenter 都是同步发送的,接受消息和发送消息是在一个线程里。这里介绍关于 NSNotificationQueue 的异步发送,通过 NSNotificationQueue 将通知添加到队列当中,立即将控制权返回给调用者,在合适的时机发送通知,从而不会阻塞当前的调用。从线程的角度看并不是真正的异步发送,或可称为延时发送,它是利用了 runloop 的时机来触发的,所以如果在其他子线程使用 NSNotificationQueue ,需要开启 runloop 。由于最终还是通过 NSNotificationCenter 进行发送通知,所以从这个角度讲它还是同步的。所谓异步,指的是非实时发送而是在合适的时机发送,并没有开启异步线程。
NSPostingStyle 和 coalesceMask 在上面的类结构中有介绍。 modes 这个就和 runloop 有关了,指的是 runloop 的 mode 。
❶ 根据 coalesceMask 参数判断是否合并通知
❷ 接着根据 postingStyle 参数,判断通知发送的时机
❸ runloop 立即回调通知方法,同步发送
❹ runloop 在执行 timer 事件或 sources 事件的时候回调通知方法,异步发送
❺ runloop 空闲的时候回调通知方法,异步发送
runloop 触发某个时机,调用 GSPrivateNotifyASAP() 和 GSPrivateNotifyIdle() 方法,这两个方法最终都调用了 notify() 方法。 notify() 所做的事情就是调用 NSNotificationCenter 的 postNotification: 进行发送通知。
异步线程发送通知则响应函数也是在异步线程,如果执行UI刷新相关的话就会出现问题,那么如何保证在主线程响应通知呢?可以使用 addObserverForName: object: queue: usingBlock 方法注册通知,指定在 mainqueue 上响应 block 。
1、首先将 epub 文件解压后得到其资源文件包,其中会包含相应的文件夹。
2、其次通过 OEBPS 文件夹中的资源文件提取所需的数据并进行拼装后渲染,包含了文件的解压缩和通过 touchXML 对 xml 数据的解析和写入。
3、最后对 xml 解析获取到节点内容并保存,遍历数据数组找到其中所需的节点,将其遍历节点得到所需属性的 name 和 value 作为字典对象填充至模型。
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流