KVO的本質(zhì)-另附runtime代碼實(shí)現(xiàn)

先上簡(jiǎn)單的背景代碼:

@interface ViewController ()
@property(nonatomic, strong) Dog *dog;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.dog = [Dog new];
    self.dog.say = @"wow";
    NSLog(@"class name before kvo:%s",object_getClassName(self.dog));
    [self.dog addObserver:self forKeyPath:@"say" options:NSKeyValueObservingOptionNew context:nil];
    NSLog(@"class name after kvo:%s",object_getClassName(self.dog));
    self.dog.say = @"yeah";
}

- (void)dealloc{
    [self.dog removeObserver:self forKeyPath:@"setSay"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@",change);
}

Log

god:wow
class name before kvo:Dog
class name after kvo:NSKVONotifying_Dog
god:yeah
{
    kind = 1;
    new = yeah;
}

可以注意到kvo之后對(duì)象dog的類型改變了。
可以猜想設(shè)置了kvo之后:
1.runtime重新生成了一個(gè)類型
2.然后將原對(duì)象的指針指向他
3.在新類型中動(dòng)態(tài)添加新的方法來(lái)轉(zhuǎn)接原來(lái)的setXX方法
3.1轉(zhuǎn)發(fā)方法給父類屬性set
3.2通知監(jiān)聽對(duì)象屬性發(fā)生了改變

#import <Foundation/Foundation.h>
#import "Dog.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation Dog

// 給子類提供的IMP
void kvoMethond(id obj,SEL sel, NSString * str) {
    // 創(chuàng)建父類實(shí)例-superDog
    struct objc_super superDog = {
        obj,
        class_getSuperclass([obj class])
    };
    // super.setName
    objc_msgSendSuper(&superDog, sel, str);
    
    NSString *selString = [NSStringFromSelector(sel) substringToIndex:NSStringFromSelector(sel).length-1];
    NSString *key = [NSStringFromSelector(sel) substringWithRange:NSMakeRange(3, selString.length - 3)];
    key = [[key substringToIndex:1].lowercaseString stringByAppendingString:[key substringFromIndex:1]];
    // 通知觀察者調(diào)用監(jiān)聽方法
    // 獲取observer
    id observer = objc_getAssociatedObject(obj, "dog_observer");
    [observer observeValueForKeyPath:key ofObject:obj change:@{@"new": str} context:nil];
}

- (void)setSay:(NSString *)say {
    _say = say;
    NSLog(@"god:%@",say);
}

// 自定義實(shí)現(xiàn)的kvo
- (void)dog_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
    
    const char* kvoClassName = [[NSString stringWithFormat:@"KVONotification_%@",NSStringFromClass(self.class)] UTF8String];
    // 動(dòng)態(tài)創(chuàng)建子類
    Class kvoClass = objc_allocateClassPair(self.class, kvoClassName, 0);
    // 將self的isa指向新的子類
    object_setClass(self, kvoClass);
    
    NSString *pathName = [NSString stringWithFormat:@"set%@:",keyPath.capitalizedString];
    SEL sel = NSSelectorFromString(pathName);
    // 給sel(setName)設(shè)置新的 IMP
    class_addMethod(kvoClass, sel, (IMP)kvoMethond, "v@:@");
    
    // 使用關(guān)聯(lián)方法保存 observer(IMP方法內(nèi)要用到這個(gè)對(duì)象)
    objc_setAssociatedObject(self, "dog_observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

vc code

    self.dog = [Dog new];
    self.dog.say = @"wow";
    NSLog(@"class name before kvo:%s",object_getClassName(self.dog));
//    [self.dog addObserver:self forKeyPath:@"say" options:NSKeyValueObservingOptionNew context:nil];
    [self.dog dog_addObserver:self forKeyPath:@"say" options:NSKeyValueObservingOptionNew context:nil];
    NSLog(@"class name after kvo:%s",object_getClassName(self.dog));
    self.dog.say = @"yeah";
    
}

- (void)dealloc{
    [self.dog removeObserver:self forKeyPath:@"setSay"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@",change);
}
最后編輯于
?著作權(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)容