KVO底層原理—利用Runtime自定義KVO

KVO:Key-value observer,也就是鍵值觀察,是Objective-C對觀察者模式的實現,每當被觀察對象的某個屬性值發生改變時,註冊的觀察者便能得到通知。
當然想了解KVO,還要先對KVC有所瞭解:KVC底層原理,本文利用Runtime實現自定義KVO,如果對Runtime不熟悉可以先了解下前幾篇文章:Runtime底層原理KVO-官網直通車

先簡單介紹一下KVO使用:

  • 添加觀察:addObserver:self forKeyPath:options:context:
  • 觀察回調:observeValueForKeyPath:ofObject :change: context:
  • 移除觀察:removeObserver: forKeyPath:

TIP:建議KVO還是手動添加移除。如果沒有移除觀察,會有隱藏奔潰隱患(單例),比如當觀察者析構時不會自動移除,被觀察對象繼續發送消息, 像發送一個消息給已經釋放的對象, 觸發exception。

KVO原理:

KVO默認觀察setter,使用isa-swizzling來實現自動鍵值觀察,也就是被觀察對象的isa會被修改,指向一個動態生成的子類NSKVONotifying_xxxx(isa在移除觀察者之後復原,動態生成的類不會被移除),但是通過object_getClass獲取的還是原來的類,該子類重寫了觀察對象的setter方法,還有classdealloc方法和_isKVOA標識,並在重寫setter方法中調用– willChangeValueForKey– didChangeValueForKey,然後向父類發送消息。如果automaticallyNotifiesObserversForKey返回NO的時候可以手動觀察

  • 動態生成子類: NSKVONotifying_xxxx,用原來的類名做後綴
  • 重寫觀察對象的setter,classdealloc方法和_isKVOA標識
  • 在重寫setter方法中調用 – willChangeValueForKey和 – didChangeValueForKey
  • 向父類發送消息

自定義KVO

知道了KVO的原理後我們利用Runtime進行驗證並自定義KVO的實現,在實現了系統KVO的功能基礎上還添加了自動移除觀察者機制、監聽利用block回調等

利用LLDB查看isa的指針,再利用Runtime查看添加觀察前後的變化,可以通過下面的方法對原來的類和新增的NSKVONotifying_xxxx類進行對比

// 遍歷方法 -- 判斷imp指針是否改變也就是重寫
- (void)getClassAllMethod:(Class)cls {
    if (!cls) return;
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(cls, &count);
    for (int i = 0; i<count; i++) {
        Method method = methodList[i];
        SEL sel = method_getName(method);
        IMP imp = class_getMethodImplementation(cls, sel);
        NSLog(@"%@ --- %p",NSStringFromSelector(sel), imp);
    }
    free(methodList);
}

// 遍歷屬性
- (void)getClassProperty:(Class)cls {
    if (!cls) return;
    //獲取類中的屬性列表
    unsigned int propertyCount = 0;
    objc_property_t * properties = class_copyPropertyList(cls, &propertyCount);
    for (int i = 0; i<propertyCount; i++) {
        NSLog(@"屬性的名稱爲 : %s",property_getName(properties[i]));
        /**
         特性編碼 具體含義
         R readonly
         C copy
         & retain
         N nonatomic
         G(name) getter=(name)
         S(name) setter=(name)
         D @dynamic
         W weak
         P 用於垃圾回收機制
         */
        NSLog(@"屬性的特性字符串爲: %s",property_getAttributes(properties[i]));
    }
    //釋放屬性列表數組
    free(properties);
}

// 遍歷變量
- (void)getClassAllIvar:(Class)cls {
    if (!cls) return;
    unsigned int count = 0;
    Ivar *ivarList = class_copyIvarList(cls, &count);
    for (int i = 0; i<count; i++) {
        Ivar ivar = ivarList[i];
        NSLog(@"%s",ivar_getName(ivar));
    }
    free(ivarList);
}

// 遍歷類以及子類
- (void)getClasses:(Class)cls {
    if (!cls) return;
    // 註冊類的總數
    int count = objc_getClassList(NULL, 0);
    // 創建一個數組,其中包含給定對象
    NSMutableArray *mArr = [NSMutableArray arrayWithObject:cls];
    // 獲取所有已註冊的類
    Class *classes = (Class *)malloc(sizeof(Class)*count);
    objc_getClassList(classes, count);
    for (int i = 0; i < count; i++) {
        if (cls == class_getSuperclass(classes[i])) {
            [mArr addObject:classes[i]];
        }
    }
    free(classes);
    NSLog(@"classes --- %@", mArr);
}

經過驗證後開始自定義KVO實現系統功能,並額外加上自定義的一些功能。先添加用來保存KVO信息的Info類VKVOInfo用來保存信息,還有一個擴展NSObject+VKVO,主要實現系統的原有功能,再添加自定義的一些方法,比如自動移除觀察者等。

  • 首先先動態生成子類,並添加setterclassdealloc方法
#pragma mark - 動態生成子類
- (Class)createChildClass:(NSString *)keyPath {
    NSString *oldName = NSStringFromClass([self class]);
    NSString *newName = [NSString stringWithFormat:@"%@%@", kVKVOPrefix, oldName];
    Class newClass = NSClassFromString(newName);
    // 如果內存不存在,創建生成新的類,防止重複創建生成新類
    if (newClass) return newClass;
    
    newClass = objc_allocateClassPair([self class], newName.UTF8String, 0);
    objc_registerClassPair(newClass);
    
    // 添加class方法
    SEL classSEL = NSSelectorFromString(@"class");
    Method classMethod = class_getInstanceMethod([self class], classSEL);
    const char *classType = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, classSEL, (IMP)v_class, classType);
    
    // 添加setter方法
    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod([self class], setterSEL);
    const char *setterType = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)v_setter, setterType);
    
    // 添加dealloc方法
    SEL deallocSEL = NSSelectorFromString(@"dealloc");
    Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
    const char *deallocType = method_getTypeEncoding(deallocMethod);
    class_addMethod(newClass, deallocSEL, (IMP)v_dealloc, deallocType);
    
    return newClass;
}
  • 把isa指針指向動態生成的KVONotifying子類(Person類會動態生成KVONotifying_Person)
object_setClass(self, newClass);
  • 保存KVO的信息
VKVOInfo *KVOInfo = [[VKVOInfo alloc] initWithObserver:observer forKeyPath:keyPath options:options handleBlock:handleBlock];
    NSMutableArray *infoArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kVKVOAssiociateKey));
    if (!infoArr) {
        infoArr = [NSMutableArray arrayWithCapacity:1];
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kVKVOAssiociateKey), infoArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [infoArr addObject:KVOInfo];

下面是部分重要代碼:在setter方法中,先將消息發送給原來的類,再利用block響應回調(這裏也可以添加判斷,利用block回調或者設置代理),也可以添加一些自定義的方法,比如去掉NSKeyValueObservingOptions參數。

static void v_setter(id self, SEL _cmd, id newValue) {
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    id oldValue = [self valueForKey:keyPath];
    /// Specifies the superclass of an instance.
    struct objc_super v_objc_super = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };
    // 消息轉發給父類
    void (*v_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
    v_msgSendSuper(&v_objc_super, _cmd, newValue);
    
    // 響應回調
    NSMutableArray *infoArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kVKVOAssiociateKey));
    for (VKVOInfo *info in infoArr) {
        if ([info.keyPath isEqualToString:keyPath]) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                if (info.options & NSKeyValueObservingOptionNew) {
                    if (info.handleBlock) {
                        info.handleBlock(info.observer, info.keyPath, info.options, newValue, oldValue);
                    }
                }
//                SEL obserSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
//                void (*v_objc_msgSend)(id, SEL, id, id, id, void *) = (void *)objc_msgSend;
//                Class supperClass = (object_getClass(self));
//                v_objc_msgSend(info.observer, obserSEL, keyPath, supperClass, @{keyPath:newValue}, NULL);
            });
        }
    }
}

這裏是Demo地址:https://github.com/JBWangWork/VCustomKVO,本Demo已更新,去掉了options和context參數(系統context可以起到快速定位觀察鍵的作用)。本Demo只適用於學習KVO底層原理。

該文章爲記錄本人的學習路程,也希望能夠幫助大家,知識共享,共同成長,共同進步!!!文章地址:https://www.jianshu.com/p/724f6ab39400

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章