小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

如何自己動(dòng)手實(shí)現(xiàn) KVO

 ccccshq 2017-05-22

如何自己動(dòng)手實(shí)現(xiàn) KVO

本文是 Objective-C Runtime 系列文章的第三篇。如果你對(duì) Objective-C Runtime 還不是很了解,可以先去看看前兩篇文章:

  1. Objective-C Runtime
  2. Method Swizzling 和 AOP 實(shí)踐

本篇會(huì)探究 KVO (Key-Value Observing) 實(shí)現(xiàn)機(jī)制,并去實(shí)踐一番 - 利用 Runtime 自己動(dòng)手去實(shí)現(xiàn) KVO 。

KVO (Key-Value Observing)

KVO 是 Objective-C 對(duì)觀察者模式(Observer Pattern)的實(shí)現(xiàn)。也是 Cocoa Binding 的基礎(chǔ)。當(dāng)被觀察對(duì)象的某個(gè)屬性發(fā)生更改時(shí),觀察者對(duì)象會(huì)獲得通知。

有意思的是,你不需要給被觀察的對(duì)象添加任何額外代碼,就能使用 KVO 。這是怎么做到的?

KVO 實(shí)現(xiàn)機(jī)制

KVO 的實(shí)現(xiàn)也依賴于 Objective-C 強(qiáng)大的 Runtime 。Apple 的文檔有簡(jiǎn)單提到過(guò) KVO 的實(shí)現(xiàn)

Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ...

Apple 的文檔真是一筆帶過(guò),唯一有用的信息也就是:被觀察對(duì)象的 isa 指針會(huì)指向一個(gè)中間類,而不是原來(lái)真正的類??磥?lái),Apple 并不希望過(guò)多暴露 KVO 的實(shí)現(xiàn)細(xì)節(jié)。不過(guò),要是你用 runtime 提供的方法去深入挖掘,所有被掩蓋的細(xì)節(jié)都會(huì)原形畢露。Mike Ash 早在 2009 年就做了這么個(gè)探究。

簡(jiǎn)單概述下 KVO 的實(shí)現(xiàn):

當(dāng)你觀察一個(gè)對(duì)象時(shí),一個(gè)新的類會(huì)動(dòng)態(tài)被創(chuàng)建。這個(gè)類繼承自該對(duì)象的原本的類,并重寫了被觀察屬性的 setter 方法。自然,重寫的 setter 方法會(huì)負(fù)責(zé)在調(diào)用原 setter方法之前和之后,通知所有觀察對(duì)象值的更改。最后把這個(gè)對(duì)象的 isa 指針 ( isa 指針告訴 Runtime 系統(tǒng)這個(gè)對(duì)象的類是什么 ) 指向這個(gè)新創(chuàng)建的子類,對(duì)象就神奇的變成了新創(chuàng)建的子類的實(shí)例。

原來(lái),這個(gè)中間類,繼承自原本的那個(gè)類。不僅如此,Apple 還重寫了 -class 方法,企圖欺騙我們這個(gè)類沒(méi)有變,就是原本那個(gè)類。更具體的信息,去跑一下 Mike Ash 的那篇文章里的代碼就能明白,這里就不再重復(fù)。

KVO 缺陷

KVO 很強(qiáng)大,沒(méi)錯(cuò)。知道它內(nèi)部實(shí)現(xiàn),或許能幫助更好地使用它,或在它出錯(cuò)時(shí)更方便調(diào)試。但官方實(shí)現(xiàn)的 KVO 提供的 API 實(shí)在不怎么樣。

比如,你只能通過(guò)重寫 -observeValueForKeyPath:ofObject:change:context: 方法來(lái)獲得通知。想要提供自定義的 selector ,不行;想要傳一個(gè) block ,門都沒(méi)有。而且你還要處理父類的情況 - 父類同樣監(jiān)聽(tīng)同一個(gè)對(duì)象的同一個(gè)屬性。但有時(shí)候,你不知道父類是不是對(duì)這個(gè)消息有興趣。雖然 context 這個(gè)參數(shù)就是干這個(gè)的,也可以解決這個(gè)問(wèn)題 - 在 -addObserver:forKeyPath:options:context: 傳進(jìn)去一個(gè)父類不知道的 context。但總覺(jué)得框在這個(gè) API 的設(shè)計(jì)下,代碼寫的很別扭。至少至少,也應(yīng)該支持 block 吧。

有不少人都覺(jué)得官方 KVO 不好使的。Mike Ash 的 Key-Value Observing Done Right,以及獲得不少分享討論的 KVO Considered Harmful 都把 KVO 拿出來(lái)吊打了一番。所以在實(shí)際開(kāi)發(fā)中 KVO 使用的情景并不多,更多時(shí)候還是用 Delegate 或 NotificationCenter。

自己實(shí)現(xiàn) KVO

如果沒(méi)找到理想的,就自己動(dòng)手做一個(gè)。既然我們對(duì)官方的 API 不太滿意,又知道如何去實(shí)現(xiàn)一個(gè) KVO,那就嘗試自己動(dòng)手寫一個(gè)簡(jiǎn)易的 KVO 玩玩。

首先,我們創(chuàng)建 NSObject 的 Category,并在頭文件中添加兩個(gè) API:

typedef void(^PGObservingBlock)(id observedObject, NSString *observedKey, id oldValue, id newValue);

@interface NSObject (KVO)

- (void)PG_addObserver:(NSObject *)observer
                forKey:(NSString *)key
             withBlock:(PGObservingBlock)block;

- (void)PG_removeObserver:(NSObject *)observer forKey:(NSString *)key;

@end

接下來(lái),實(shí)現(xiàn) PG_addObserver:forKey:withBlock: 方法。邏輯并不復(fù)雜:

  1. 檢查對(duì)象的類有沒(méi)有相應(yīng)的 setter 方法。如果沒(méi)有拋出異常;
  2. 檢查對(duì)象 isa 指向的類是不是一個(gè) KVO 類。如果不是,新建一個(gè)繼承原來(lái)類的子類,并把 isa 指向這個(gè)新建的子類;
  3. 檢查對(duì)象的 KVO 類重寫過(guò)沒(méi)有這個(gè) setter 方法。如果沒(méi)有,添加重寫的 setter 方法;
  4. 添加這個(gè)觀察者
- (void)PG_addObserver:(NSObject *)observer
                forKey:(NSString *)key
             withBlock:(PGObservingBlock)block
{
    // Step 1: Throw exception if its class or superclasses doesn't implement the setter
    SEL setterSelector = NSSelectorFromString(setterForGetter(key));
    Method setterMethod = class_getInstanceMethod([self class], setterSelector);
    if (!setterMethod) {
        // throw invalid argument exception
    }

    Class clazz = object_getClass(self);
    NSString *clazzName = NSStringFromClass(clazz);

    // Step 2: Make KVO class if this is first time adding observer and 
    //          its class is not an KVO class yet
    if (![clazzName hasPrefix:kPGKVOClassPrefix]) {
        clazz = [self makeKvoClassWithOriginalClassName:clazzName];
        object_setClass(self, clazz);
    }

    // Step 3: Add our kvo setter method if its class (not superclasses) 
    //          hasn't implemented the setter
    if (![self hasSelector:setterSelector]) {
        const char *types = method_getTypeEncoding(setterMethod);
        class_addMethod(clazz, setterSelector, (IMP)kvo_setter, types);
    }

    // Step 4: Add this observation info to saved observation objects
    PGObservationInfo *info = [[PGObservationInfo alloc] initWithObserver:observer Key:key block:block];
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
    if (!observers) {
        observers = [NSMutableArray array];
        objc_setAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [observers addObject:info];
}

再來(lái)一步一步細(xì)看。

第一步里,先通過(guò) setterForGetter() 方法獲得相應(yīng)的 setter 的名字(SEL)。也就是把 key 的首字母大寫,然后前面加上 set 后面加上 :,這樣 key 就變成了 setKey:。然后再用 class_getInstanceMethod 去獲得 setKey: 的實(shí)現(xiàn)(Method)。如果沒(méi)有,自然要拋出異常。

第二步,我們先看類名有沒(méi)有我們定義的前綴。如果沒(méi)有,我們就去創(chuàng)建新的子類,并通過(guò) object_setClass() 修改 isa 指針。

- (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName
{
    NSString *kvoClazzName = [kPGKVOClassPrefix stringByAppendingString:originalClazzName];
    Class clazz = NSClassFromString(kvoClazzName);

    if (clazz) {
        return clazz;
    }

    // class doesn't exist yet, make it
    Class originalClazz = object_getClass(self);
    Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0);

    // grab class method's signature so we can borrow it
    Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));
    const char *types = method_getTypeEncoding(clazzMethod);
    class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);

    objc_registerClassPair(kvoClazz);

    return kvoClazz;
}

動(dòng)態(tài)創(chuàng)建新的類需要用 objc/runtime.h 中定義的 objc_allocateClassPair() 函數(shù)。傳一個(gè)父類,類名,然后額外的空間(通常為 0),它返回給你一個(gè)類。然后就給這個(gè)類添加方法,也可以添加變量。這里,我們只重寫了 class 方法。哈哈,跟 Apple 一樣,這時(shí)候我們也企圖隱藏這個(gè)子類的存在。最后 objc_registerClassPair() 告訴 Runtime 這個(gè)類的存在。

第三步,重寫 setter 方法。新的 setter 在調(diào)用原 setter 方法后,通知每個(gè)觀察者(調(diào)用之前傳入的 block ):

static void kvo_setter(id self, SEL _cmd, id newValue)  
{
    NSString *setterName = NSStringFromSelector(_cmd);
    NSString *getterName = getterForSetter(setterName);

    if (!getterName) {
        // throw invalid argument exception
    }

    id oldValue = [self valueForKey:getterName];

    struct objc_super superclazz = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };

    // cast our pointer so the compiler won't complain
    void (*objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper;

    // call super's setter, which is original class's setter method
    objc_msgSendSuperCasted(&superclazz, _cmd, newValue);

    // look up observers and call the blocks
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
    for (PGObservationInfo *each in observers) {
        if ([each.key isEqualToString:getterName]) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                each.block(self, getterName, oldValue, newValue);
            });
        }
    }
}

細(xì)心的同學(xué)會(huì)發(fā)現(xiàn)我們對(duì) objc_msgSendSuper 進(jìn)行類型轉(zhuǎn)換。在 Xcode 6 里,新的 LLVM 會(huì)對(duì) objc_msgSendSuper 以及 objc_msgSend 做嚴(yán)格的類型檢查,如果不做類型轉(zhuǎn)換。Xcode 會(huì)抱怨有 too many arguments 的錯(cuò)誤。(在 WWDC 2014 的視頻 What new in LLVM 中有提到過(guò)這個(gè)問(wèn)題。)

最后一步,把這個(gè)觀察的相關(guān)信息存在 associatedObject 里。觀察的相關(guān)信息(觀察者,被觀察的 key, 和傳入的 block )封裝在 PGObservationInfo 類里。

@interface PGObservationInfo : NSObject

@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) PGObservingBlock block;

@end

就此,一個(gè)基本的 KVO 就可以 work 了。當(dāng)然,這只是一個(gè)一天多做出來(lái)的小東西,會(huì)有 bug,也有很多可以優(yōu)化完善的地方。但作為 demo 演示如何利用 Runtime 動(dòng)態(tài)創(chuàng)建類、如何實(shí)現(xiàn) KVO,足已。

完整的例子可以從這里下載:ImplementKVO

如果有任何問(wèn)題或找到 bug,可以郵件我 peng@ 或者私信我的微博 @no_computer。

謝謝觀賞。

Reference

KVO Implementation

Creating Classes at Runtime in Objective-C

Key-Value Observing Done Right

By your command

Associated Objects

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多