KVO拾遗

作者: lmfei | 来源:发表于2019-12-30 22:56 被阅读0次

    KVO定义

    KVO全称Key Value Observing,根据runtime实现的,是观察者设计模式的一种实现,由NSKeyValueObserving协议提供支持,NSObject类实现了该协议,因此所有NSObject的子类都可以使用KVO

    KVO使用

    使用三部曲:
    1. 注册监听
    - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
    

    observer:观察者
    keyPath:观察的属性路径
    options:观察值的类型 NSKeyValueObservingOptionNew-新值 NSKeyValueObservingOptionOld-老值
    content:上下文,快速定位观察属性

    1. 实现监听代理
    - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
    

    keyPath:观察的属性路径
    content:上下文
    change中包括的值
    kind:
    //set方法
    NSKeyValueChangeSetting = 1,
    //插入
    NSKeyValueChangeInsertion = 2,
    //删除
    NSKeyValueChangeRemoval = 3,
    //替换
    NSKeyValueChangeReplacement = 4,复制代码
    new:新值 观察属性时options设置了NSKeyValueObservingOptionNew,会返回
    old:旧值 观察属性时options设置了NSKeyValueObservingOptionOld,会返回

    1. 移除监听
    - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
    

    注:一定要移除,不然会出现莫名的崩溃问题

    手动观察\自动观察

    重写

    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;
    

    默认返回YES,返回NO则不自动观察属性,需要观察属性需要在对应setter方法设置值的前后,执行

    - (void)willChangeValueForKey:(NSString *)key;
    - (void)didChangeValueForKey:(NSString *)key;
    

    例:

    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
        if ([key isEqualToString:@"name"]) {
            return NO;
        }
        return YES;
    }
    - (void)setName:(NSString *)name {
        //手动添加观察者
        [self willChangeValueForKey:@"name"];
        _name = name;
        [self didChangeValueForKey:@"name"];
    }
    
    多属性决定一个属性的值

    实现

    + (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
    

    例:

    + (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
        NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
        if ([key isEqualToString:@"proportion"]) {
            NSArray *affectingKeys = @[@"height", @"width"];
            keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
        }
        return keyPaths;
    }
    
    - (CGFloat)proportion {
        return self.width/self.height;
    }
    
    集合属性的监听

    例:

    //LAnimal.h
    @property (nonatomic, strong) NSMutableArray *actions;
    
    //ViewController.h
    @property (nonatomic, retain) LAnimal *animal;
    
    //ViewController.m/viewDidLoad
        [self.animal addObserver:self forKeyPath:@"actions" options:(NSKeyValueObservingOptionNew) context:NULL];
    
    //使用此方法集合属性的无法监听
    [self.animal.actions addObject:@"fly"];
    
    //需要使用KVC 集合的setter方法
    [[self.animal mutableArrayValueForKey:@"actions"] addObject:@"run"];//kind:2
    [[self.animal mutableArrayValueForKey:@"actions"] removeLastObject];//kind:3
    [self.animal mutableArrayValueForKey:@"actions"][0] = @"eat";//kind:4
    

    KVO原理探索

    1. KVO观察的是setter方法

    通过监听实例变量与属性

    //LAnimal.h
    {
        @public
        NSString *secondName;
    }
    @property (nonatomic, copy) NSString *name;
    
    //ViewController.m/viewDidLoad
    [self.animal addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:@"animal_name"];
    [self.animal addObserver:self forKeyPath:@"secondName" options:NSKeyValueObservingOptionNew context:@"second_name"];
    
    //然后设置值
    self.animal.name = @"Lucy";
    self.animal->secondName = @"haha";
    

    通过上面代码,可以发现实例变量并没有被KVO监听,而属性是被监听的,推断KVO监听的处理可能是在setter方法

    1. 在被监听后,类前后的变化
    //ViewController.m
    - (void)catClasses: (Class)cls {
        int count = objc_getClassList(NULL, 0);
        NSMutableArray *tmpAry = [NSMutableArray arrayWithObject:cls];
        Class *clses = (Class *)malloc(sizeof(Class)*count);
        objc_getClassList(clses, count);
        for (int i=0; i < count; i++) {
            if (cls == class_getSuperclass(clses[i])) {
                [tmpAry addObject:clses[i]];
            }
        }
        free(clses);
        NSLog(@"%s\n%@",__FUNCTION__, tmpAry);
    }
    
    //ViewController.m/viewDidLoad
    [self catClasses:[LAnimal class]];
    [self.animal addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:@"animal_name"];
    [self.animal addObserver:self forKeyPath:@"secondName" options:NSKeyValueObservingOptionNew context:@"second_name"];
    NSLog(@"************************");
    [self catClasses:[LAnimal class]];
    

    打印信息:

    2019-12-25 16:53:25.960926+0800 KVODemo[38333:4314339] -[ViewController catClasses:]
    (
        LAnimal
    )
    2019-12-25 16:53:32.088372+0800 KVODemo[38333:4314339] ************************
    2019-12-25 16:53:32.093974+0800 KVODemo[38333:4314339] -[ViewController catClasses:]
    (
        LAnimal,
        "NSKVONotifying_LAnimal"
    )
    

    通过打印信息可以知道属性被监听后,会重新生成LAnimal的一个子类NSKVONotifying_LAnimal

    1. 查看生成的子类哪些方法发生了变化
    self.animal = [LAnimal new];
    [self.animal addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:@"animal_name"];
    NSLog(@"************************");
    [self catMethods:NSClassFromString(@"NSKVONotifying_LAnimal")];
    

    打印信息:

    2019-12-25 17:06:44.514170+0800 KVODemo[38621:4331805] .cxx_destruct----0x10e85e4d0
    2019-12-25 17:06:44.514424+0800 KVODemo[38621:4331805] name----0x10e85e460
    2019-12-25 17:06:44.514507+0800 KVODemo[38621:4331805] setName:----0x10e85e490
    2019-12-25 17:06:48.723917+0800 KVODemo[38621:4331805] ************************
    2019-12-25 17:06:50.295125+0800 KVODemo[38621:4331805] setName:----0x10ebb8b5e
    2019-12-25 17:06:50.295294+0800 KVODemo[38621:4331805] class----0x10ebb7592
    2019-12-25 17:06:50.295430+0800 KVODemo[38621:4331805] dealloc----0x10ebb7336
    2019-12-25 17:06:50.295503+0800 KVODemo[38621:4331805] _isKVOA----0x10ebb732e
    

    通过打印信息可以看到setName方法imp发生了变变化即方法被重写了,并新增了class、dealloc、_isKVOA三个方法,到这可以验证1中的猜测

    原理概述:

    当类的对象被第一次观察时,会在运行期动态生成一个该类的派生类,并在这个派生类中重写被观察属性的setter方法,通知机制就是在这个setter方法中实现的,例:原类A -> NSKVONotifying_A,每一个对象都有一个isa指针指向当前类,当这个类被观察时,isa指针就会指向新的派生类,被观察属性被赋值时执行的是新类的setter方法,键值观察的通知主要依赖于NSObject的两个方法,willChangeValueForKey和didChangValueForKey,前者是被观察属性发生变化前调用记录就旧值,改变之后后者被调用,最后会调用observeValueForKeyPath代理方法

    生活如此美好,今天就点到为止。。。

    相关文章

      网友评论

          本文标题:KVO拾遗

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