iOS窺探KVO底層實現實戰篇
點擊上方「
程序員大咖
」,選擇「置頂公眾號」
關鍵時刻,第一時間送達!
來源:大兵布萊恩特
https://www.jianshu.com/p/dc89f0a2d1ac
程序員大咖整理髮布,轉載請聯繫作者獲得授權
文講到 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 即可
【點擊成為源碼大神】
▼點擊「
閱讀原文
」進入程序員商城
推薦閱讀:
※現實的社會,現實的人,寫得太絕了!誰看誰服!
※人心換不到人心,句句入人心,句句好現實
※2016年VR虛擬現實會成為流行趨勢嗎?
※現實與夢幻結合的剪影攝影
※暗物質處於深層現實中?時空觀念或將被拋棄