自己動手實現(xiàn)簡單KVO

1. KVO原理

??KVO的實現(xiàn)原理相信大家都應該有所了解, 就是在對象A為屬性B添加監(jiān)聽addObserver: forKeyPath: options: context:的時候會自動為類C(就是對象A的類)創(chuàng)建一個派生類D(子類), 并且在類D中重寫被監(jiān)聽屬性的setter方法E. 并且會把你被監(jiān)聽對象的isa指針由原來的C指向D, 當你調用被監(jiān)聽屬性的setter方法時, 方法E會被執(zhí)行. (這里不懂的話可以拿出紙筆在紙上畫出來, 更好理解)

2.實現(xiàn)KVO

??根據上面的原理我們知道, 實際上KVO就是Runtime的應用:
??①因為在調用addObserver:的時候會動態(tài)添加一個類newC, 所以這里會動態(tài)創(chuàng)建一個類;
??②動態(tài)添加類之后我們還需要重寫被監(jiān)聽對象的setter方法, 我們還需要為newC動態(tài)添加添加一個method;

??我們開始先來模仿系統(tǒng)實現(xiàn)KVO的方式.

// 添加監(jiān)聽
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context 

// 移除監(jiān)聽
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath 
// 監(jiān)聽值的變化
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context

上面這三個方法是我們在使用KVO會用到的三個方法, 現(xiàn)在我們來嘗試著自己寫這三個方法, 創(chuàng)建一個NSObject的category

@interface NSObject (KVO)
// 添加監(jiān)聽
- (void)ls_addObserver:(NSObject *)observer forKey:(NSString *)key;
// 移除監(jiān)聽
- (void)ls_removeObserver:(NSObject *)observer forKey:(NSString *)key;

@end

@interface NSObject (Observering)
// 監(jiān)聽值變化
- (void)observeNewValue:(NSObject *)newValue;
@end

先來看添加監(jiān)聽的方法, 這里的任務是①動態(tài)創(chuàng)建一個被監(jiān)聽類的派生類; ②為整個派生類添加被監(jiān)聽屬性的setter方法;③修改被監(jiān)聽對象的isa指針, 使其指向派生類;

- (void)ls_addObserver:(NSObject *)observer forKey:(NSString *)key {
    Class cls = [self class];
    NSString* clsName = NSStringFromClass(cls);
    
    NSString* setKey = [NSString stringWithFormat:@"set%@%@:", [key substringToIndex:1].uppercaseString, [key substringFromIndex:1]];
    SEL setterSelector = NSSelectorFromString(setKey);
    Method setterMethod = class_getInstanceMethod(cls, setterSelector);
    const char* types = method_getTypeEncoding(setterMethod);
    
    // 1.動態(tài)創(chuàng)建一個類
    Class newClss = objc_allocateClassPair(cls, [NSString stringWithFormat:@"%@%@", kLsClassNamePrefix, clsName].UTF8String, 0);
    // 2.添加方法, 需要自己寫setMethod
    class_addMethod(newClss, setterSelector, (IMP)setMethod, types);
    
    objc_registerClassPair(newClss);
    // 3.替換isa指針
    object_setClass(self, newClss);
    
    // 記錄下對象的key和監(jiān)聽者
    NSMutableArray* servers = objc_getAssociatedObject(self, kKey.UTF8String);
    if (!servers) {
        servers = [NSMutableArray arrayWithCapacity:0];
        objc_setAssociatedObject(self, kKey.UTF8String, servers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    ObserverInfo* info = [[ObserverInfo alloc] initWithObserver:observer key:key];
    [servers addObject:info];
}

正如上面說的, 這里是有三個任務,

// 這里是添加的替換方法
static void setMethod(id self, SEL _cmd, id newValue) {
    NSString* setterName = NSStringFromSelector(_cmd);
    NSString* getterName = [setterName substringFromIndex:4];
    getterName = [NSString stringWithFormat:@"%@%@", [setterName substringWithRange:NSMakeRange(3, 1)].lowercaseString, getterName];
    getterName = [getterName substringToIndex:getterName.length - 1];
    
    // setter方法的任務有兩個
    // 1. 告訴所有的監(jiān)聽此屬性的對象, 對象屬性發(fā)生改變
    NSMutableArray* observers = objc_getAssociatedObject(self, kKey.UTF8String);
    for (ObserverInfo* info in observers) {
        if ([info.key isEqualToString:getterName]) {
            if ([info.observer respondsToSelector:@selector(observeNewValue:)]) {
                [info.observer performSelector:@selector(observeNewValue:) withObject:newValue];
            }
        }
    }
    
    // 2. 調用父類的setter方法
    struct objc_super superClass = {
        .receiver = self,
        .super_class = class_getSuperclass([self class])
    };
    // 這里直接調用objc_msgSendSuper會發(fā)生錯誤
    void(*objc_msgSendSuperCasted)(void *, SEL, id) = (void*)objc_msgSendSuper;
    
    objc_msgSendSuperCasted(&superClass, _cmd, newValue);
}

這里是添加的setter方法

// 移除監(jiān)聽的方法
- (void)ls_removeObserver:(NSObject *)observer forKey:(NSString *)key {
    NSMutableArray* servers = objc_getAssociatedObject(self, kKey.UTF8String);
    ObserverInfo* info = nil;
    for (ObserverInfo* observerInfo in servers) {
        if (observerInfo.observer == observer && observerInfo.key == key) {
            info = observerInfo;
            break;
        }
    }
    [servers removeObject:info];
}

然后在對應的監(jiān)聽者中實現(xiàn)- (void)observeNewValue:(NSObject *)newValue;就可以了.
當然本文旨在讓大家明白整體的流程, 這里的流程只是簡單的實現(xiàn)了KVO, 里面還有很多的流程判斷和注意事項就不一一贅述了.

源碼地址
https://github.com/autmaple/SimpleKVO

參考資料
http://tech.glowing.com/cn/implement-kvo/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容