上文講到 iOS KVO 底層實現原理https://www.jianshu.com/p/0aa83ac521ba,大概就是runtime時候動態的建立一個子類,並重寫了子類的 setter dealloc class 等方法,將當前類的 isa 指標指向這個子類,這樣就不會影響原有類的實現
上圖可以看到 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 實現
從程式碼執行來看 p1新增了 kvo 監聽 ,當p1.name 發生兩次改變時候 都有呼叫 observeValueForKeyPath方法
p2沒有新增 kvo 監聽 所以只是簡單的呼叫了 person 的 setter 方法
以上程式碼只是對 KVO 做了一個比較簡單的實現 ,並沒有做一些釋放操作 將子類 isa 指標重新執行 Person 類 ,本文只探究原理 實際開發中可能並不會自己去實現 KVO, 只需要呼叫系統 API 即可
好了,我是大兵布萊恩特,歡迎加入博主技術交流群,iOS 開發交流群