iOS KVO原理的探究實(shí)現(xiàn)

感覺(jué)很久都沒(méi)有寫新文章了,還是寫一些小文章吧,記錄一下也好!
這一次和大家講解的是KVO -- key value observing,想必大家也不陌生,項(xiàng)目中也會(huì)有用到的地方,網(wǎng)上相關(guān)的文章也不少,我在這里的目的就是為了班門弄斧??,哈哈哈...。但是大家有沒(méi)有研究過(guò)KVO是怎么實(shí)現(xiàn)的呢?我本著厚顏無(wú)恥的心態(tài)為大家講解一下。

前言
KVO的監(jiān)聽,實(shí)際上是通過(guò)運(yùn)行時(shí)runtime實(shí)現(xiàn)的,并且對(duì)被監(jiān)聽的對(duì)象,動(dòng)態(tài)創(chuàng)建其子類,重寫setter、class、等方法,改變其isa指針的指向,企圖瞞騙我們。想一想都覺(jué)得好過(guò)分??

雖然我們不知道其內(nèi)部的實(shí)現(xiàn),但是通過(guò)猜想,說(shuō)錯(cuò)了,不是猜想,是實(shí)踐。首先我們來(lái)看看添加觀察后,過(guò)程發(fā)生了什么。這里已經(jīng)有大神做過(guò)分析了,可以先看看這里
我也簡(jiǎn)單的給大家講解下

@interface TestObject : NSObject

@property (nonatomic,assign) int z;    // 被觀察的屬性z

@end

@implementation TestObject

@end


@interface ViewController ()

@end

//MARK: - 獲取方法列表
static NSArray *ClassMethodNames(Class c)
{
    NSMutableArray *array = [NSMutableArray array];
    
    unsigned int methodCount = 0;
    Method *methodList = class_copyMethodList(c, &methodCount);
    unsigned int i;
    for(i = 0; i < methodCount; i++)
        [array addObject: NSStringFromSelector(method_getName(methodList[i]))];
    free(methodList);
    
    return array;
}

//MARK: - 打印對(duì)象信息
static void PrintDescription(NSString *name, id obj)
{
    NSString *str = [NSString stringWithFormat:
                     @"%@: %@\n\tNSObject class %s\n\tlibobjc class %s\n\timplements methods <%@>",
                     name,
                     obj,
                     class_getName([obj class]),
                     class_getName(object_getClass(obj)),
                     [ClassMethodNames(object_getClass(obj)) componentsJoinedByString:@", "]];
    printf("%s\n", [str UTF8String]);
}


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // 創(chuàng)建 x、y對(duì)象
    TestObject *x = [TestObject new];
    TestObject *y = [TestObject new];

    // 觀察 y 對(duì)象 
    [y addObserver:y forKeyPath:@"z" options:0 context:nil];
    
    // 打印x、y對(duì)象有什么不同
    PrintDescription(@"x", x);
    PrintDescription(@"y", y);
    
}
以下為打印信息
x: <TestObject: 0x604000017b90>
    NSObject class TestObject
    libobjc class TestObject
    implements methods <z, setZ:>

y: <TestObject: 0x604000017c50>
    NSObject class TestObject
    libobjc class NSKVONotifying_TestObject
    implements methods <setZ:, class, dealloc, _isKVOA>

從結(jié)果我們可以看出,被觀察的對(duì)象重寫了 setter class dealloc 還有一個(gè)神秘的_isKVOA方法,原來(lái)的TestObject類也發(fā)生了改變,變成了NSKVONotifying_TestObject,這樣子我們就可以斷定對(duì)象方法class被覆蓋了override,但我們可以通過(guò)runtimeobject_getClass()獲取其真正的類名,而且[obj class]其內(nèi)部實(shí)現(xiàn)就是下面的樣子??

- (Class)class {
    return object_getClass(self);
}

你還會(huì)發(fā)現(xiàn),當(dāng)你重寫TestObjectclass方法,這方法已經(jīng)不走了,原因就是被重寫了。來(lái),讓我們?cè)僦貙懸恍┓椒?,看?code>Apple還做了什么事情。

@interface TestObject : NSObject

@property (nonatomic,assign) int z;

@end

@implementation TestObject

- (void)setZ:(int)z {
    _z = z;
    NSLog(@"setter z:%d",z);
}

- (void)willChangeValueForKey:(NSString *)key {
    NSLog(@"willChangeValueForKey:%@",key);
}

- (void)didChangeValueForKey:(NSString *)key {
    NSLog(@"didChangeValueForKey:%@",key);
}

- (Class)class {
    NSLog(@"not override:%@",self);
    return object_getClass(self);
}


@end
- (void)viewDidLoad {
    [super viewDidLoad];

    TestObject *x = [TestObject new];
    TestObject *y = [TestObject new];

    [y addObserver:y forKeyPath:@"z" options:0 context:nil];

    x.z = 1;
    NSLog(@"separation line");
    y.z = 1;

    [x class];
    NSLog(@"separation line1");
    [y class];
}

}

2018-07-19 11:17:04.989201+0800 KVO_demo[79056:7685866] setter z:1
2018-07-19 11:17:04.989368+0800 KVO_demo[79056:7685866] separation line
2018-07-19 11:17:04.989613+0800 KVO_demo[79056:7685866] willChangeValueForKey:z
2018-07-19 11:17:04.989775+0800 KVO_demo[79056:7685866] setter z:1
2018-07-19 11:17:04.990139+0800 KVO_demo[79056:7685866] didChangeValueForKey:z
2018-07-19 11:17:04.990517+0800 KVO_demo[79056:7685866] not override:<TestObject: 0x60000001acf0>
2018-07-19 11:17:05.007865+0800 KVO_demo[79056:7685866] separation line1
這次我們重寫了4個(gè)方法

- (void)setZ:(int)z
- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key
- (Class)class
通過(guò)觀察打印信息,我們可以發(fā)現(xiàn)被觀察的對(duì)象,在賦值之前會(huì)先走- (void)willChangeValueForKey:(NSString *)key,然后是- (void)setZ:(int)z,最后走到- (void)didChangeValueForKey:(NSString *)key,但并沒(méi)有走- (Class)class,而 x 對(duì)象只走了 setter方法和class方法。這樣子我們就可以看出差別了,y 對(duì)象的class方法確實(shí)被覆蓋了。不過(guò)細(xì)心的你是不是發(fā)現(xiàn)了一些問(wèn)題,上面不是說(shuō)過(guò)setter方法也被覆蓋重寫了嗎?那為什么 y 對(duì)象的setter方法還會(huì)走,有貓膩啊??,這個(gè)我們下面會(huì)說(shuō)到,看官別急。
在這里還要提醒一下大家,當(dāng)你重寫override-(void)willChangeValueForKey:(NSString *)key ;- (void)didChangeValueForKey:(NSString *)key其中的一個(gè)方法時(shí),下面監(jiān)聽的回調(diào)方法就不會(huì)再調(diào)用的了。

@interface NSObject(NSKeyValueObserving)

- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;

@end

而且此回調(diào)方法是根據(jù)添加的觀察者是誰(shuí)addObserver:observer,誰(shuí)就會(huì)接收到通知。

小結(jié)
根據(jù)以上的分析和實(shí)踐,我們大致了解KVO一個(gè)看得見(jiàn)的實(shí)現(xiàn)流程,下面我們來(lái)模擬一下KVO的實(shí)現(xiàn)。還原案發(fā)現(xiàn)場(chǎng)罒ω罒。

先上代碼
#import <Foundation/Foundation.h>

typedef NSString *CCKeyValueChangeKey NS_STRING_ENUM;

FOUNDATION_EXPORT CCKeyValueChangeKey const CCKeyValueChangeNewKey;
FOUNDATION_EXPORT CCKeyValueChangeKey const CCKeyValueChangeOldKey;

@interface NSObject (Override_KVO)


/**
 對(duì)象方法,誰(shuí)調(diào)用 表明 誰(shuí)就是被觀察的對(duì)象

 @param observer 觀察者
 @param key 被觀察對(duì)象的屬性
 @param handler block回調(diào)
 */
- (void)cc_addObserver:(id)observer
                   key:(NSString *)key
               handler:(void(^)(id observer, NSString *key, NSDictionary<CCKeyValueChangeKey, id> *dic))handler;


/**
 回調(diào)方法

 @param key 被觀察對(duì)象的屬性
 @param object 觀察者
 @param change 變化前后的值
 */
- (void)cc_observeValueForKey:(NSString *)key object:(id)object change:(NSDictionary<CCKeyValueChangeKey,id> *)change;

/**
 移除觀察者

 @param observer 觀察者
 @param key 被觀察對(duì)象的屬性
 */
- (void)removeObserver:(id)observer key:(NSString *)key;

@end

我們先來(lái)看一下頭文件的一些屬性和方法

typedef NSString *CCKeyValueChangeKey NS_STRING_ENUM;

FOUNDATION_EXPORT CCKeyValueChangeKey const CCKeyValueChangeNewKey;
FOUNDATION_EXPORT CCKeyValueChangeKey const CCKeyValueChangeOldKey;

這里是參考Apple的實(shí)現(xiàn)

typedef NSString * NSKeyValueChangeKey NS_STRING_ENUM;
/* Keys for entries in change dictionaries. See the comments for -observeValueForKeyPath:ofObject:change:context: for more information.
*/
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeKindKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNewKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeOldKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeIndexesKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNotificationIsPriorKey API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

至于這個(gè)typedef NSString *CCKeyValueChangeKey和這個(gè)FOUNDATION_EXPORT有什么用,請(qǐng)百度一下吧,這里就不細(xì)說(shuō)??,往下還有很多知識(shí)點(diǎn),根本說(shuō)不完。簡(jiǎn)單理解就是自定義字符串。

我們先看一下第一個(gè)方法的實(shí)現(xiàn)

- (void)cc_addObserver:(id)observer key:(NSString *)key handler:(void (^)(id, NSString *, NSDictionary<CCKeyValueChangeKey,id> *))handler {
    
    SEL setterMethod = NSSelectorFromString(setterNameFunc(key));
    
    Class observerClass = object_getClass(self);    // 獲取類名
    
    NSString *classString = NSStringFromClass(observerClass);   // 轉(zhuǎn)字符串
    
    // 判斷是否已創(chuàng)建子類
    if (![classString hasPrefix:cc_keyValueNotifiy]) {
        
        Class subClass = [self createSubClass:observerClass];
        
        object_setClass(self, subClass);    // 原類轉(zhuǎn)換為子類
    }
    
    
    // 沒(méi)有 setter 方法 就添加
    if (![self isExistSetterFunc:setterMethod]) {
        
        Method method = class_getInstanceMethod(object_getClass(self), setterMethod);
        
        const char *type = method_getTypeEncoding(method);
        
        class_addMethod(object_getClass(self), setterMethod, (IMP)cc_setter, type);
    }
    
    // 創(chuàng)建回調(diào)對(duì)象
    CCObserver *temp = [[CCObserver alloc] initObserver:observer key:key handler:handler];
    
    //  關(guān)聯(lián)數(shù)組,存儲(chǔ)對(duì)象
    NSMutableArray *array = objc_getAssociatedObject(self, (__bridge void *)cc_associationKey);
    
    if (!array) {
        
        array = [NSMutableArray array];
        
        objc_setAssociatedObject(self, (__bridge void *)cc_associationKey, array, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    [array addObject:temp];
}

在添加觀察者的時(shí)候,我們先判斷一下是否已經(jīng)新建了子類,沒(méi)有的話我們就創(chuàng)建其子類,改變其isa的指向,下面我們看一看代碼

Class subClass = [self createSubClass:observerClass];
//MARK: - 創(chuàng)建子類
- (Class)createSubClass:(Class)obsererClass {
    
    NSString *classString = NSStringFromClass(obsererClass);
    
    NSString *subString = [NSString stringWithFormat:@"%@%@",cc_keyValueNotifiy,classString];   // 拼接
    
    Class subClass = NSClassFromString(subString);      // 轉(zhuǎn)換為 class
    
    // 不是nil 證明已存在該類,直接返回
    if (subClass) {
        return subClass;
    }
    
    subClass = objc_allocateClassPair(obsererClass, subString.UTF8String, 0);      // 創(chuàng)建新的類
    
    Method method = class_getInstanceMethod(obsererClass, @selector(class));
    
    const char * type = method_getTypeEncoding(method);
    
    class_addMethod(subClass, @selector(class), (IMP)override_classFunc, type);     // 添加新的class方法 以重載原class方法
    
    objc_registerClassPair(subClass);       // 創(chuàng)建新的類后,注冊(cè)該類,使添加的方法 屬性生效
    
    return subClass;
}

我們通過(guò)runtimeobjc_allocateClassPair動(dòng)態(tài)的創(chuàng)建一個(gè)新的類,順便看一下官方文檔的說(shuō)明,需要什么參數(shù),每個(gè)參數(shù)的作用。
superclass: 這個(gè)參數(shù)將作為新類的父類,如果是nil,那么會(huì)創(chuàng)建一個(gè)新的根類。
name:新類的名字
extraBytes分配字節(jié)數(shù),通常為 0
如果已經(jīng)存在該類,那么新建的類就會(huì)失敗,返回nil,不過(guò)上面的代碼已經(jīng)做了判斷了。

/** 
 * Creates a new class and metaclass.
 * 
 * @param superclass The class to use as the new class's superclass, or \c Nil to create a new root class.
 * @param name The string to use as the new class's name. The string will be copied.
 * @param extraBytes The number of bytes to allocate for indexed ivars at the end of 
 *  the class and metaclass objects. This should usually be \c 0.
 * 
 * @return The new class, or Nil if the class could not be created (for example, the desired name is already in use).
 * 
 * @note You can get a pointer to the new metaclass by calling \c object_getClass(newClass).
 * @note To create a new class, start by calling \c objc_allocateClassPair. 
 *  Then set the class's attributes with functions like \c class_addMethod and \c class_addIvar.
 *  When you are done building the class, call \c objc_registerClassPair. The new class is now ready for use.
 * @note Instance methods and instance variables should be added to the class itself. 
 *  Class methods should be added to the metaclass.
 */
OBJC_EXPORT Class _Nullable
objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, 
                       size_t extraBytes) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

新類已經(jīng)創(chuàng)建完了,我們繼續(xù)通過(guò)runtimeclass_addMethod,動(dòng)態(tài)添加新的方法,以覆蓋原有的class方法

    Method method = class_getInstanceMethod(obsererClass, @selector(class));
    
    const char * type = method_getTypeEncoding(method);
    
    class_addMethod(subClass, @selector(class), (IMP)override_classFunc, type);

最后注冊(cè)新的類,上面的文檔也提到了,當(dāng)創(chuàng)建完新的類,為其設(shè)置類的屬性和函數(shù),最后調(diào)用objc_registerClassPair進(jìn)行注冊(cè),以告訴編譯器新的類已經(jīng)準(zhǔn)備好使用。
getClass自然就有setClass,創(chuàng)建完新的類后,沒(méi)錯(cuò),要變身了,請(qǐng)后退。object_setClass可以把對(duì)象設(shè)置為任何類,很好很強(qiáng)大的。而且你會(huì)發(fā)現(xiàn)我們很多的類都是這樣子設(shè)計(jì)的,這里先舉個(gè)??NSNumber,先說(shuō)這么多,我們繼續(xù)吧。

    object_setClass(self, subClass);    // 改變`isa`的指向

新類創(chuàng)建完了,外衣也做好了,接下來(lái)我們?yōu)樾骂愒賱?dòng)態(tài)添加一個(gè)方法,以重寫父類的setter方法,并且把相關(guān)信息存儲(chǔ)到數(shù)組里面,繼續(xù)以runtimeobjc_setAssociatedObject關(guān)聯(lián)起來(lái),又是一個(gè)知識(shí)點(diǎn)。還有我們?yōu)槭裁匆陆ㄒ粋€(gè)對(duì)象來(lái)存儲(chǔ)信息呢,這里是為了后續(xù)的回調(diào)使用的,而且這樣子包一層,也為了防止強(qiáng)引用observer,而造成內(nèi)存無(wú)法釋放。數(shù)組會(huì)強(qiáng)引用其對(duì)象,所以不宜直接使用。

    if (![self isExistSetterFunc:setterMethod]) {
        
        Method method = class_getInstanceMethod(object_getClass(self), setterMethod);
        
        const char *type = method_getTypeEncoding(method);
        
        class_addMethod(object_getClass(self), setterMethod, (IMP)cc_setter, type);
    }
 // 創(chuàng)建回調(diào)對(duì)象
    CCObserver *temp = [[CCObserver alloc] initObserver:observer key:key handler:handler];
    
    //  關(guān)聯(lián)數(shù)組,存儲(chǔ)對(duì)象
    NSMutableArray *array = objc_getAssociatedObject(self, (__bridge void *)cc_associationKey);
    
    if (!array) {
        
        array = [NSMutableArray array];
        
        objc_setAssociatedObject(self, (__bridge void *)cc_associationKey, array, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    [array addObject:temp];

我們繼續(xù)看一下重寫的setter 又做了什么,來(lái)看下面的代碼。我們?cè)谫x值前先調(diào)用其NSObject分類方法willChangeValueForKey:,之后獲取原來(lái)的值,創(chuàng)建一個(gè)結(jié)構(gòu)體struct objc_super,又是一個(gè)知識(shí)點(diǎn),這里最好能理解superself之間的關(guān)系,例如:[[super alloc] init]這段代碼,意思是調(diào)用父類的方法分配內(nèi)存、初始化,但是其接收接者是self。
((void (*)(id, SEL, id)) (void *)objc_msgSendSuper)((__bridge id)(&superClass), _cmd, (id)newValue);沒(méi)錯(cuò),這里的做法是為了讓原類TestObjectsetter方法生效,而這里為什么用objc_msgSendSuper而不是objc_msgSend,我想大家應(yīng)該明白了吧,這里也很好的解釋了上文提到的 setter方法為什么重寫了還會(huì)走,因?yàn)橛酶割?code>[super setXXX:]了。

//MARK: - 重載 setter 方法
static void cc_setter(id self, SEL _cmd, id newValue) {
    
    NSString *setterName = NSStringFromSelector(_cmd);
    
    NSString *key = getterName(setterName);
    
    [self willChangeValueForKey:key];       // 賦值前 調(diào)用 willChangeValueForKey
    
    id oldValue = [self valueForKey:key];  // 獲取 舊值
    
    // 創(chuàng)建結(jié)構(gòu)體
    struct objc_super superClass = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self)),
    };
    
    // 使原類 setter 方法生效
    ((void (*)(id, SEL, id)) (void *)objc_msgSendSuper)((__bridge id)(&superClass), _cmd, (id)newValue);
    
    // 獲取存儲(chǔ)的數(shù)組
    NSMutableArray *array = objc_getAssociatedObject(self, (__bridge void *)cc_associationKey);
    
    for (CCObserver *observer in array) {
        
        NSDictionary *dic = @{CCKeyValueChangeOldKey:oldValue, CCKeyValueChangeNewKey:newValue};
        
//        [observer.observer cc_observeValueForKey:key object:observer.observer change:dic];.
        
        !observer.handler ? : observer.handler(observer.observer, key, dic);      // 執(zhí)行回調(diào)
    }
    
    [self didChangeValueForKey:key];        // 賦值后 調(diào)用 didChangeValueForKey
    
}

謎底基本都差不多揭開了,最后我們來(lái)把結(jié)果回調(diào)一下,把存儲(chǔ)起來(lái)的對(duì)象進(jìn)行回調(diào)。注釋掉的那段代碼是模仿原有的回調(diào),這里我們以block形式回調(diào)。

 // 獲取存儲(chǔ)的數(shù)組
    NSMutableArray *array = objc_getAssociatedObject(self, (__bridge void *)cc_associationKey);
    
    for (CCObserver *observer in array) {
        
        NSDictionary *dic = @{CCKeyValueChangeOldKey:oldValue, CCKeyValueChangeNewKey:newValue};
        
//        [observer.observer cc_observeValueForKey:key object:observer.observer change:dic];.
        
        !observer.handler ? : observer.handler(observer.observer, key, dic);      // 執(zhí)行回調(diào)
    }
    

總結(jié)
KVO涉及到知識(shí)點(diǎn)非常多,而且很多是runtime,對(duì)這方面的薄弱的同學(xué)很可能看不懂,需要多看多讀多寫,反復(fù)消化,這段代碼為什么這么寫,而不這么寫,都需要認(rèn)真理解后才知道。這里還可以延伸很多其他的知識(shí)。消息的轉(zhuǎn)發(fā)、通過(guò)關(guān)聯(lián)值為分類添加屬性、動(dòng)態(tài)添加方法、類的指向及其結(jié)構(gòu)、繼承、分類的作用...,多得不要不要的。而我們這次的實(shí)踐也很好的證明了KVO確實(shí)是通過(guò)runtime,動(dòng)態(tài)創(chuàng)建一個(gè)新類作為原對(duì)象的子類,把isa指針指向新類,并對(duì)其重寫setter方法,以監(jiān)聽屬性值的變化來(lái)通知外界。

下一次分享什么好呢,我自己都很期待??

本次的demo

示例及demo的參考來(lái)源及文章

iOS開發(fā)·KVO用法,原理與底層實(shí)現(xiàn)
How Key-Value Observing (KVO) is actually implemented at the runtime level

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容