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虛擬現實會成為流行趨勢嗎?
現實與夢幻結合的剪影攝影
暗物質處於深層現實中?時空觀念或將被拋棄

TAG:現實 | 底層 | 實戰 | 實現 |