iOS窺探KVO底層實現實戰篇

大兵布萊恩特0409發表於2018-07-12

上文講到 iOS KVO 底層實現原理https://www.jianshu.com/p/0aa83ac521ba,大概就是runtime時候動態的建立一個子類,並重寫了子類的 setter dealloc class 等方法,將當前類的 isa 指標指向這個子類,這樣就不會影響原有類的實現

image.png

上圖可以看到 KVO內部執行順序

今天我們就 kvo 內部執行順序 也通過 runtime 動態建立子類方式去實現.

第一步動態建立一個 NSKVONotifying_Person 子類


/**
 執行時動態的建立子類

 @param super_cls 父類
 @return 返回子類
 */
- (Class) registerSubClassWithSuperClass:(Class)super_cls  {
	///動態的建立 子類
	NSString *clsName = [NSString stringWithFormat:@"NSKVONotifying_%@",super_cls];
	///一個 NSObject 預設分配16個位元組記憶體
	Class sub_cls = objc_allocateClassPair(super_cls,clsName.UTF8String,16);
	///註冊一個子類
	objc_registerClassPair(sub_cls);
	///將父類 isa 指標指向 子類
	object_setClass(self, sub_cls);
	return sub_cls;
}

複製程式碼

第二步動態的給這個子類 動態新增方法 setter 方法 didChangeValueForKey方法 class 方法實現


	///動態建立子類  NSKVONotifying_xxx
	Class sub_cls = [self registerSubClassWithSuperClass:super_cls];
	
	///給子類動態的新增 class setter  didChangeValueForKey 實現
	Method class_method = class_getInstanceMethod(super_cls, @selector(class));
	Method changeValue_method = class_getInstanceMethod(super_cls, @selector(didChangeValueForKey:));
	
	class_addMethod(sub_cls, @selector(class), (IMP)kvo_class,method_getTypeEncoding(class_method));
	///給子類動態的新增 didChangeValueForKey
	class_addMethod(sub_cls, @selector(didChangeValueForKey:), (IMP)didChangeValue,method_getTypeEncoding(changeValue_method));
	///動態的給子類新增 setter 方法
	class_addMethod(sub_cls, setterSel, (IMP)kvo_setter,method_getTypeEncoding(method));
	
	///將觀察者物件跟當前例項 self 關聯起來
	objc_setAssociatedObject(self,(__bridge const void * _Nonnull)(KVOAssociatedObservers), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

複製程式碼

重寫 class 方法實現


/**
 自實現 class 方法

 @param self 當前類實現
 @param _cmd  class
 @return  返回父類 Class 外界不會知道 NSKVONotifying_子類存在
 */
static Class kvo_class(id self,SEL _cmd) {
	return class_getSuperclass(object_getClass(self));
}


複製程式碼

重寫 setter 方法實現


/**
 自實現 setter 方法

 @param self 當前類實現
 @param _cmd  setter
 @param newValue  賦值
 */
static void kvo_setter(id self,SEL _cmd,id newValue) {
	
	NSString *setterName = NSStringFromSelector(_cmd);
	NSString *getterName = getterForSetter(setterName);

	///將要改變屬性的值
	[self willChangeValueForKey:getterName];
	
	///呼叫 super setter 方法
	struct objc_super suer_cls = {
		.receiver = self,
		.super_class = class_getSuperclass(object_getClass(self))
	};
	
	///儲存舊值
	objc_setAssociatedObject(self,(__bridge const void * _Nonnull)(KVOAssociatedOldValue),[self valueForKey:getterName], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
	///呼叫父類 setter 方法 設定新值
	objc_msgSendSuper(&suer_cls,_cmd,newValue);
	///改變監聽屬性值後 呼叫 didChangeValueForKey 並在內部 呼叫
	[self didChangeValueForKey:getterName];
	
};

複製程式碼

重寫 didChangeValueForKey 方法實現


/**
 didChangeValueForkey 實現方法 , 當根據 SEL (didChangeValueForkey:) 會找到方法 IMP 實現
 */
static void didChangeValue(id self,SEL _cmd,NSString *key) {
	
	id newValue = [self valueForKey:key];
	id observer = objc_getAssociatedObject(self,(__bridge const void * _Nonnull)(KVOAssociatedObservers));
	id oldValue = objc_getAssociatedObject(self,(__bridge const void * _Nonnull)(KVOAssociatedOldValue));
	
	NSMutableDictionary *change = [NSMutableDictionary dictionary];
	if (oldValue) {
		change[@"oldValue"] = oldValue;
	} else {
		change[@"oldValue"] = [NSNull null];
	}
	if (newValue) {
		change[@"newValue"] = newValue;
	} else {
		change[@"newValue"] = newValue;
	}
	
	[observer observeValueForKeyPath:key ofObject:self change:change context:NULL];

}

複製程式碼

通過以上的步驟 就可以模擬 KVO 內部實現 ,寫出來一個自己的 KVO 實現

image.png

從程式碼執行來看 p1新增了 kvo 監聽 ,當p1.name 發生兩次改變時候 都有呼叫 observeValueForKeyPath方法

p2沒有新增 kvo 監聽 所以只是簡單的呼叫了 person 的 setter 方法

以上程式碼只是對 KVO 做了一個比較簡單的實現 ,並沒有做一些釋放操作 將子類 isa 指標重新執行 Person 類 ,本文只探究原理 實際開發中可能並不會自己去實現 KVO, 只需要呼叫系統 API 即可

好了,我是大兵布萊恩特,歡迎加入博主技術交流群,iOS 開發交流群

QQ20180712-0.png

相關文章