iOS通知底層實現原理

在iOS9.0以前,通知中心對觀察者對象進行unsafe_unretained引用,當被引用的對象釋放時不會自動置爲nil,指針仍指向該內存空間,形成了野指針,引發EXC_BAD_ACCESS崩潰。git

在iOS9以後,不對觀察對象進行移除也不會形成崩潰,這是由於通知中心對觀察者作了弱引用,對象銷燬時會對對象的指針置空。在代碼編寫過程當中,基於嚴謹性,最好在註冊了通知以後,也要removeObserver。github

那麼通知中心是怎麼實現對觀察者的引用呢?要了解這一點,咱們先看一下通知的實現機制。因爲蘋果對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

複製代碼

執行以下代碼: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類:

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
複製代碼
相關文章
相關標籤/搜索