在iOS9.0之前,通知中心对观察者对象进行unsafe_unretained引用,当被引用的对象释放时不会自动置为nil,指针仍指向该内存空间,造成了野指针,引起EXC_BAD_ACCESS崩溃。
在iOS9之后,不对观察对象进行移除也不会造成崩溃,这是因为通知中心对观察者做了弱引用,对象销毁时会对对象的指针置空。在代码编写过程中,基于严谨性,最好在注册了通知之后,也要removeObserver。
那么通知中心是怎么实现对观察者的引用呢?要了解这一点,我们先看一下通知的实现机制。由于苹果对Foundation源码是不开源的,我们具体就参考一下GNUStep的源码实现。GNUStep的源码地址为:github.com/gnustep/lib…, 具体源码可以进行查看。
- (void) addObserver: (id)observer selector: (SEL)selector name: (NSString*)name object: (id)object
添加观察者之后,会创建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
执行如下代码:
[[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的观察者,会接受到通知。
通知是同步执行的,发送通知所在的线程和通知接收回调处理在同一线程进行处理,测试代码如下
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}
发送通知 <NSThread: 0x2821f14c0>{number = 5, name = (null)}
处理通知 <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类:
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
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
//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
NSMapTable
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