NSNotification
概念1
NSNotification 的实际是一种观察者模式
不能跨应用程序进程通信
解决应用程序内部不同对象之间解耦而设计
分析系统文件 NSNotification.h
发现里面定义了两个类型
@interface NSNotification : NSObject <NSCopying, NSCoding>
@interface NSNotification (NSNotificationCreation)
@interface NSNotificationCenter : NSObject
使用
NSNotification 属性 :
三个属性均是只读 :
@property (readonly, copy) NSNotificationName name;
@property (nullable, readonly, retain) id object;
@property (nullable, readonly, copy) NSDictionary *userInfo;
NSNotification 初始化:
初始化一个NSNotification对象可以使用 - initWithName , - initWithCoder ,
+ notificationWithName
但是不能使用init 方法 :
- (instancetype)init /*NS_UNAVAILABLE*/; /* do not invoke; not a
valid initializer for this class */
系统注释说明init方法不允许使用
NSNotificationCenter :
单例, 通过只读属性 defaultCenter 返回center 对象 :
@property (class, readonly, strong) NSNotificationCenter *defaultCenter;
包含添加, 发送,移除通知方法 :
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable
NSNotificationName)aName object:(nullable id)anObject;
//直接block回调
- (id <NSObject>)addObserverForName:(nullable
NSNotificationName)name object:(nullable id)obj queue:(nullable
NSOperationQueue *)queue usingBlock:(void (^)(NSNotification
*note))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0),
tvos(9.0));
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:
(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)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;
NSNotificationCenter
管理着监听者的注册和注销,通知的发送和接收
维护着一个通知的分发表,把所有通知发送者发送的通知,转发给对应的监听者
们
是一个单例,通过 [NSNotificationCenter defaultCenter] 方法获取
notificationName 为 nil,则会接收所有的通知
通过notificationName区分
同一条通知的多个观察者(注册者)收到消息和返回消息不是依据注册时间顺序的
通知中心默认是以同步的方式发送通知的
发送通知的对象只有在通知都发送完毕后,才能进行下一步代码, 也就是发送过程
对象被堵塞
NSNotificationQueue 通知队列 :
管理多个通知的调用
是队列, 肯定是先进先出的顺序发送队列里的通知了
发送方式 :
NSPostWhenIdle:空闲发送通知
runloop处于空闲时
NSPostASAP:尽快发送通知
当前通知调用或者计时器结束发出通知
NSPostNow :同步发送通知
合并通知完成之后立即发出(不合并也是同步发送)
发送通知的流程 :
发送通知的流程总体来讲就是根据NotifcationName查找到对应的Observer链
表,
然后遍历整个链表,给每个Observer结点中保持存的对象及SEL,来向对象发送
信息(也即是调用对象的SEL方法)
合并通知 :
通知可以进行合并, 合并后相同归类的通知只发送一次
3种 :
NSNotificationNoCoalescing:不合并通知
NSNotificationCoalescingOnName:合并相同名称的通知
NSNotificationCoalescingOnSender:合并相同通知和同一对象的通知
内存释放 :
1. 普通的注册方法在iOS 9 以后可以不手动释放(iOS 9 以前要)
而在 iOS 9 以后,通知中心持有的是注册者的 weak 指针, 对象被销毁会自动释
放内存
2. 注册通知使用回调的方法, 需要手动释放,移除通知
usingBlock 内部对 对象进行一次强引用
线程 :
接收通知的线程,和发送通知所处的线程是同一个线程。也就是说如果如果要在
接收通知的时候更新UI,需要注意发送通知的线程是否为主线程
如果要在主线程收到通知, 则在主队列调用发送方法
为了保证更新UI在主线程上, 使用
dispatch_async(dispatch_get_main_queue(), ^{}); 保险
通过MachPort向特定线程发送通知 :
需求 :
如果我想在某个子线程接收通知 (比如某个后台监听) , 那么我就需要将通知发向
特定的线程
首先 观察者对象需要的实例变量:
一个保存通知的可变数组
一个通知正确线程的MachPort
一个防止通知数组内多线程处理冲突的锁
一个通知线程
实现:
1. 属性
/**
notifications 存储子线程发出的通知的队列
thread 处理通知事件的预期线程
lock 用于对通知队列加锁的锁对象,避免线程冲突
port 用于向期望线程发送信号的通信端口
*/
var notifications: [NSNotification]!
var thread: Thread!
var lock: NSLock!
var port: NSMachPort!
2.初始化
在注册通知之前,你需要初始化这些属性 :
/**
对相关的成员属性进行初始
*/
func setUpThreadingSupport() {
guard notifications == nil else { return }
notifications = [NSNotification]()
thread = Thread.current
lock = NSLock()
port = NSMachPort()
port.setDelegate(self)
//创建了一个MachPort,它将被添加到当前线程的RunLoop中
RunLoop.current.add(port, forMode: .commonModes)
}
3.
/**
从子线程收到MachPort发出的消息后所执行的方法
在该方法中从队列中获取子线程中发出的NSNotification
然后使用当前线程来处理该通知
RunLoop收到MacPort发出的消息时所执行的回调方法。
*/
func handleMachMessage(_ msg: UnsafeMutableRawPointer) {
lock.lock()
while notifications.count > 0 {
let notification = notifications[0]
notifications.remove(at: 0)
lock.unlock()
processNotification(notification)
lock.lock()
}
lock.unlock()
}
4.转给指定线程
收到通知后, 接收通知的方法必须确定是该线程, 再进行处理 , 如果不是那就将其
放入队列, 并交由通知端口 :
func processNotification(_ notification: NSNotification) {
if Thread.current == thread {
//处理出队列中的通知
}else{//将通知转发到正确的线程
lock.lock()
//将其他线程中发过来的通知不做处理,入队列暂存
notifications.append(notification)
lock.unlock()
//通过MachPort给处理通知的线程发送通知,使其处理队列中所暂存的
队列
port.send(before: Date(), msgid: 100, components:nil, from: nil,
reserved: 0)
}
}
5. 注册通知 :
前面的准备工作完成后, 实际注册之前需要先初始化 :
self.setUpThreadingSupport() //初始化
//注册时指定通知selector方法
NotificationCenter.default.addObserver(self, selector:
#selector(ViewController.processNotification(note:)), name:
NSNotification.Name(rawValue: "NotificationName"), object: nil)
移除
普通注册移除:
[[NSNotificationCenter defaultCenter] removeObserver:self
name:@“postName” object:nil];
usingBlock注册移除:
需要判断监听者是否为空:
if(_observer){
[[NSNotificationCenter defaultCenter] removeObserver:_observer];
}
通知的底层实现原理 :
原理 :
结构源码:
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;
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;
添加观察者的流程 :
所有的添加通知操作最后都会调用到addObserver: selector: name: object:
1.首先会根据传入的参数,实例化一个链表Observation。
这个Observation保存了观察者对象、接收到通知观察者对所执行的方法,由于
Observation是一个链表,还保存了下一个Observation的地址。
2.根据是否传入通知的Name选择在Named Table还是UNamed Table操作。
如果传入通知的Name,则会先去用Name去查找是否已经有对应的Value(注意这
个时候返回的Value是一个Table)
如果没有对应的Value,则创建一个新的Table,然后将这个Table以Name为Key
添加到Named Table。如果有Value,那么直接去取出这个Table
传入通知名字为时,直接根据object去对应的链表
3.得到了保存Observation的Table之后,就通过传入的object去拿对应的链表。
如果object为空,会默认有一个key表示传入object为空的情况,取的时候也会
直接用这个key去取。表示所有任何地方发送通知都会监听。
如果在保存Observation的Table中根据object作为key没有找到对应的链表,则
会创建一个节点,作为头结点插入进去;如果找到了则直接在链表末尾插入之前
实例化好的Observation
发送通知的流程 :
发送通知的流程总体来讲就是根据NotifcationName查找到对应的Observer链
表,然后遍历整个链表,给每个Observer结点中保持存的对象及SEL,来像对象
发送信息(也即是调用对象的SEL方法)
1.首先会定义一个数组ObserversArray来保存需要通知的Observer。
之前在添加观察者的时候把既没有传入NotifcationName也没有传入object保存
在了wildcard。因为这样的观察者会监听所有NotifcationName的通知,所以先
把wildcard链表遍历一遍,将其中的Observer加到数组中ObserversArray
2.找到以object为key的Observer链表
这个过程分为在Named Table中找,以及在UNamed Table中查找。然后将遍历
找到的链表,同样加入到最开始创建的数组ObserversArray中
3.遍历这个ObserversArray数组,一次取出区中的Observer结点
因为这个几点保存了观察者对象以及selector
移除通知的流程:
删除数组ObserversArray对应的Observer
根据通知name移除Observer链表相关数据, 释放对象