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链表相关数据, 释放对象