在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 複製代碼