天天看點

kvo實作原理_iOS-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方法,還有class、dealloc方法和_isKVOA辨別,并在重寫setter方法中調用– willChangeValueForKey和– didChangeValueForKey,然後向父類發送消息。如果automaticallyNotifiesObserversForKey傳回NO的時候可以手動觀察

動态生成子類: NSKVONotifying_xxxx,用原來的類名做字尾

重寫觀察對象的setter,class、dealloc方法和_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

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

NSLog(@"屬性的名稱為 : %s",property_getName(properties[i]));

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

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,主要實作系統的原有功能,再添加自定義的一些方法,比如自動移除觀察者等。

首先先動态生成子類,并添加setter,class、dealloc方法

#pragma mark - 動态生成子類複制代碼

(Class)createChildClass:(NSString )keyPath { NSStringoldName = 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底層原理。