iOS通知底层实现原理

3,264 阅读4分钟

在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