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
复制代码
相关文章
相关标签/搜索