在iOS9.0以前,通知中心对观察者对象进行unsafe_unretained引用,当被引用的对象释放时不会自动置为nil,指针仍指向该内存空间,形成了野指针,引发EXC_BAD_ACCESS崩溃。git
在iOS9以后,不对观察对象进行移除也不会形成崩溃,这是由于通知中心对观察者作了弱引用,对象销毁时会对对象的指针置空。在代码编写过程当中,基于严谨性,最好在注册了通知以后,也要removeObserver。github
那么通知中心是怎么实现对观察者的引用呢?要了解这一点,咱们先看一下通知的实现机制。因为苹果对Foundation源码是不开源的,咱们具体就参考一下GNUStep的源码实现。GNUStep的源码地址为:github.com/gnustep/lib…, 具体源码能够进行查看。数组
添加观察者以后,会建立Observation对象,存储了观察者,SEL等; Observation的结构为:缓存
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; 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; 复制代码
NSNOtifocation维护了GSIMapTable表的结构,用于存储Observation,分别是nameless,named,cache,nameless用于存储没有传入名字的通知,named存储传入了名字的通知,cache用于快速缓存。 这里值得注意的是,nameless和named虽然同为hash表,可是其结构以下:安全
在nameless表中:
GSIMapTable的结构以下
object : Observation
object : Observation
object : Observation
----------------------------
在named表中:
GSIMapTable结构以下:
name : maptable
name : maptable
name : maptable
maptable的结构以下
object : Observation
object : Observation
object : Observation
复制代码
执行以下代码:bash
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification) name:@"Test" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification) name:@"Test" object:@"12"]; [[NSNotificationCenter defaultCenter] postNotificationName:@"Test" object:@"123" userInfo:nil]; 通知发送以后,相同name,object相同或者object为nil的观察者,会接受到通知。 复制代码
通知是同步执行的,发送通知所在的线程和通知接收回调处理在同一线程进行处理,测试代码以下markdown
NSLog(@"注册通知 %@", [NSThread currentThread]); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification) name:@"Test" object:@"123"]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"发送通知 %@", [NSThread currentThread]); [[NSNotificationCenter defaultCenter] postNotificationName:@"Test" object:@"123" userInfo:nil]; }); - (void)handleNotification { NSLog(@"处理通知 %@", [NSThread currentThread]); } 复制代码
执行结果:数据结构
注册通知 <NSThread: 0x2821f4a80>{number = 1, name = main}less
发送通知 <NSThread: 0x2821f14c0>{number = 5, name = (null)}async
处理通知 <NSThread: 0x2821f14c0>{number = 5, name = (null)}**
那么map表是怎么对observer进行弱引用的呢,默认状况下,数组对数组中的元素都是进行强持有,那么咱们怎么能够实现弱引用呢?首先看一下Swift中对集合类型元素的弱引用
定义一个类:
class Pencil { var type: String var price: Double init(_ type: String, _ price: Double) { self.type = type self.price = price } } 复制代码
class CXDWeakArray: NSObject { func testRetainCount() { print("测试开始 \(CFGetRetainCount(Pencil("2B", 1.0) as CFTypeRef))") //测试开始 1 let pencil2B = Pencil("2B", 1.0) let pencilHB = Pencil("HB", 2.0) print("对象初始化 \(CFGetRetainCount(pencil2B as CFTypeRef))") print("对象初始化 \(CFGetRetainCount(pencilHB as CFTypeRef))") //对象初始化 2 //对象初始化 2 let pencilBox = [pencil2B, pencilHB] print("强引用数组 \(CFGetRetainCount(pencil2B as CFTypeRef))") print("强引用数组 \(CFGetRetainCount(pencilHB as CFTypeRef))") //强引用数组 3 //强引用数组 3 } } 复制代码
WeakArray<Element: AnyObject> { private var items: [WeakBox<Element>] = [] init(_ elements: [Element]) { items = elements.map{ WeakBox($0) } } } extension WeakArray: Collection { var startIndex: Int { return items.startIndex } var endIndex: Int { return items.endIndex } subscript(_ index: Int) -> Element? { return items[index].unbox } func index(after idx: Int) -> Int { return items.index(after: idx) } } 复制代码
测试代码
let weakPencilBox1 = WeakArray([pencil2B, pencilHB]) print("弱引用数组 \(CFGetRetainCount(pencil2B as CFTypeRef))") print("弱引用数组 \(CFGetRetainCount(pencilHB as CFTypeRef))") //弱引用数组 3 //弱引用数组 3 let firstElement = weakPencilBox1.filter { $0 != nil }.first print("元素类型 \(firstElement!!.type)") //元素类型 2B print("结束 \(CFGetRetainCount(pencil2B as CFTypeRef))") print("结束 \(CFGetRetainCount(pencilHB as CFTypeRef))") //结束 4 //结束 3 /** // 4 3 Note: 这里的 4 是由于 firstElement 持有(Retain)了 pencil2B,致使其引用计数增 1 */ 复制代码
NSPointerArray是Array的一个替代品,主要区别在于它不存储对象,而是存储对象的指针 这种类型的数组能够管理弱引用也能够管理强引用,取决于它是如何被初始化的。
NSPointerArray.weakObjects()
NSPointerArray.strongObjects()
复制代码
let weakPencilBox2 = NSPointerArray.weakObjects() let pencil2BPoiter = Unmanaged.passUnretained(pencil2B).toOpaque() let pencilHBPoiter = Unmanaged.passUnretained(pencilHB).toOpaque() print("PoiterArray初始化 \(CFGetRetainCount(pencil2B as CFTypeRef))") print("PoiterArray初始化 \(CFGetRetainCount(pencilHB as CFTypeRef))") //PoiterArray初始化 4 //PoiterArray初始化 3 weakPencilBox2.addPointer(pencil2BPoiter) weakPencilBox2.addPointer(pencilHBPoiter) print("PoiterArray结束 \(CFGetRetainCount(pencil2B as CFTypeRef))") print("PoiterArray结束 \(CFGetRetainCount(pencilHB as CFTypeRef))") //PoiterArray结束 4 //PoiterArray结束 3 复制代码
使用NSPointerArray对于存储对象和保持弱引用很是有用,可是它不是类型安全的,编译器没法推断隐含在NSPointerArray内部的对象的类型,由于它使用的是AnyObject型对象的指针。
不仅是数组,集合类型的数据结构对其中的元素默认都是强引用。
//NSHashTable -NSSet let weakPencilSet = NSHashTable<Pencil>(options: .weakMemory) weakPencilSet.add(pencil2B) weakPencilSet.add(pencilHB) print("NSHashTable Set \(CFGetRetainCount(pencil2B as CFTypeRef))") print("NSHashTable Set \(CFGetRetainCount(pencilHB as CFTypeRef))") //NSHashTable Set 4 //NSHashTable Set 3 复制代码
class Eraser { var type: String init(_ type: String) { self.type = type } } //NSMapTable -- NSDictionary let weakPencilDict = NSMapTable<Eraser, Pencil>(keyOptions: .strongMemory, valueOptions: .weakMemory) let paintingEraser = Eraser("Painting") weakPencilDict.setObject(pencil2B, forKey: paintingEraser) print("NSMapTable NSDictionary \(CFGetRetainCount(pencil2B as CFTypeRef))") print("NSMapTable NSDictionary \(CFGetRetainCount(pencilHB as CFTypeRef))") //NSMapTable NSDictionary 4 //NSMapTable NSDictionary 3 复制代码