美文网首页
KVO的本质-另附runtime代码实现

KVO的本质-另附runtime代码实现

作者: zolobdz | 来源:发表于2020-01-10 14:10 被阅读0次

    先上简单的背景代码:

    @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之后对象dog的类型改变了。
    可以猜想设置了kvo之后:
    1.runtime重新生成了一个类型
    2.然后将原对象的指针指向他
    3.在新类型中动态添加新的方法来转接原来的setXX方法
    3.1转发方法给父类属性set
    3.2通知监听对象属性发生了改变

    #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) {
        // 创建父类实例-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]];
        // 通知观察者调用监听方法
        // 获取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);
    }
    
    // 自定义实现的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];
        // 动态创建子类
        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)设置新的 IMP
        class_addMethod(kvoClass, sel, (IMP)kvoMethond, "v@:@");
        
        // 使用关联方法保存 observer(IMP方法内要用到这个对象)
        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);
    }
    

    相关文章

      网友评论

          本文标题:KVO的本质-另附runtime代码实现

          本文链接:https://www.haomeiwen.com/subject/nbspactx.html