iOS 深入理解KVO實現 | 掘金技術徵文

Junyiii發表於2017-04-21

KVO的使用

要實現will/didChangeValueForKey:方法

kvo的例項 實際在執行時被呼叫

- (void)willChangeValueForKey:(NSString *)key;  
- (void)didChangeValueForKey:(NSString *)key;複製程式碼

觸發

- (void)observeValueForKeyPath:(NSString *)keyPath  
                      ofObject:(id)object  
                        change:(NSDictionary *)change  
                       context:(void *)context;複製程式碼

因此要實現KVO,需要下列條件之一即可

  1. 實現了KVC
  2. 有訪問器方法(KVO後,會執行時重寫setter方法,新增willChangeValueForKey,didChangeValueForKey)
  3. 顯示呼叫will/didChangeValueForKey:

KVO實現原理

依賴於Runtime

  1. isa的改變
  2. 動態建立子類
  3. 替換重寫setter方法
  4. 重寫class方法

動態建立被觀察物件的子類,重寫setter方法,並且要管理一個物件的所有觀察者.

  1. 動態建立子類
// 子類名
NSString *kvoclassName = [kXJYKVOPrefix stringByAppendingString:originalclassName];
Class class = NSClassFromString(kvoclassName);

if (class) {
return class;
}

// class doesn't exist yet, make it
Class originalclass = object_getClass(self);
Class kvoclass = objc_allocateClassPair(originalclass, kvoclassName.UTF8String, 0);

// 獲得簽名
Method classMethod = class_getInstanceMethod(originalclass, @selector(class));
const char *types = method_getTypeEncoding(classMethod);
// 替換setter 實現
class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);
objc_registerClassPair(kvoclass);

return kvoclass;複製程式碼
  1. 改變isa指標
    isa指標的作用: isa指標指向例項的類(對於這裡的情況)
    例項通過isa指標找到類,可以得到方法列表,屬性列表等資訊
    當我們將isa指標指向子類時,就可以呼叫子類的方法,使用子類的屬性等。
    於是,呼叫該例項的setter方法其實是呼叫了子類的setter方法

  2. 管理觀察者
    由於一個物件可能被多個觀察者觀察,所以可以用關聯物件的方法來管理所有的觀察者。

    XJYObservationInfo *info = [[XJYObservationInfo alloc]initWithObserver:self key:key block:block];
    // 維護改KVO觀察者陣列
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kXJYKVOAssociatedObservers));
    if (!observers) {
    observers = [NSMutableArray array];
    objc_setAssociatedObject(self, (__bridge const void *)(kXJYKVOAssociatedObservers), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [observers addObject:info];複製程式碼
  3. 重寫class方法

class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);
static Class kvo_class(id self, SEL _cmd)
{
return class_getSuperclass(object_getClass(self));
}複製程式碼

額外擴充

還記得Aspects中 對於KVO的特殊處理嗎,KVO改變了例項物件的isa指標,在此處 Aspects對KVO過的例項進行了特殊的處理
Aspects:

// Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
else if (statedClass != baseClass) {
return aspect_swizzleClassInPlace(baseClass);
}複製程式碼
object.class 由於KVO重寫了class方法,所以不能準確的找到類
object_getClass()方法可以準確的找到isa指標

object.classobject_getClass(object)進行判斷 來防止KVO導致的AOP無效複製程式碼

「掘金技術徵文」
juejin.im/post/58d8e9…

相關文章