美文网首页底层
iOS-OC底层18:KV0原理

iOS-OC底层18:KV0原理

作者: MonKey_Money | 来源:发表于2020-10-28 16:10 被阅读0次

    1.概念

    KVO(Key-Value-Observer)也就是观察者模式(键值观察),是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件,一般继承自NSObject的对象都默认支持KVO

    2.基本使用

    2.1.实现基本的监听

    //定义类
    @interface PWPerson : NSObject
    
    @property (nonatomic, copy) NSString *name;
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        self.view.backgroundColor = [UIColor whiteColor];
        self.person = [[PWPerson alloc] init];
        self.person.name = @"pw";
        [self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];   
    }
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        self.person.name = [NSString stringWithFormat:@"%@++",self.person.name];
    }
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        NSLog(@"%@",change);
    }
    -(void)dealloc {
        [self.person removeObserver:self forKeyPath:@"name"];
    }
    

    打印日志

    {
        kind = 1;
        new = "pw++";
    }
     {
        kind = 1;
        new = "pw++++";
    }
    

    需要注意的两点

    1.addObserver:forKeyPath:options:context: 中context存在的意义
    如果在一个UIViewController中,同一个类的两个对象监听同一个forKeyPath,我们在observeValueForKeyPath:ofObject:change:context的判断就会比较多,keyPath和object两者都要判断,如果我们在添加观察者时context值不为空,我们在这里就可以直接判断context了
    2.removeObserver:forKeyPath:是必须的吗?
    一般情况下不会有问题,如果观察的对象是一个单利,当被观察的KeyPath改变时可能会向一个野指针发送消息。

    2.2.KVO自动触发改成手动触发

    重写automaticallyNotifiesObserversForKey方法
    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
        if ([key isEqual:@"name"]) {
            return NO;
        }
        return YES;
    }
    

    当我们点击屏幕修改name属性时,没有收到信息,我们怎么改动才会收到信息呢
    重写set方法

    -(void)setName:(NSString *)name {
        [self willChangeValueForKey:@"name"];
        _name = [name copy];
        [self didChangeValueForKey:@"name"];
    }
    

    我们收到信息

    {
        kind = 1;
        new = "pw++++++++";
    }
    

    2.3 嵌套层次

    @interface PWPerson : NSObject
    @property (nonatomic, copy) NSString *firstName;
    
    @property (nonatomic, copy) NSString *secondName;
    
    @property (nonatomic, copy) NSString *allName;
    @end
    @implementation PWPerson
    + (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
        NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
        if ([key isEqualToString:@"allName"]) {
            NSArray *affectingkeys = @[@"firstName",@"secondName"];
            keyPaths = [keyPaths setByAddingObjectsFromArray:affectingkeys];
        }
        return keyPaths;
    }
    -(NSString *)allName {
        return [NSString stringWithFormat:@"%@-----%@",self.firstName,self.secondName];
    }
    @end
    self.person.firstName = @"first";
        self.person.secondName = @"second";
        [self.person addObserver:self forKeyPath:@"allName" options:(NSKeyValueObservingOptionNew) context:NULL];
    
    //触发更改
        self.person.firstName = [NSString stringWithFormat:@"%@++",self.person.firstName];
        self.person.secondName = [NSString stringWithFormat:@"%@++",self.person.secondName];
    

    打印日志

     {
        kind = 1;
        new = "first++++++++-----second++++++++";
    }
    

    allName是有firstName和secondName合成而来的,firstName和secondName的更改决定了allName,所以我们增加了嵌套,firstName和secondName决定了allName的值

    2.3 观察数组

    我们在被观察类里添加可变数组

    @property (nonatomic, strong) NSMutableArray *hobbyArray;
    //观察对象的可变数组
        [self.person addObserver:self forKeyPath:@"hobbyArray" options:(NSKeyValueObservingOptionNew) context:NULL];
    
    //向数组中添加数据
        [self.person.hobbyArray addObject:@"footBall"];
    
    

    但是我们没有收到信息,这是为什么呢?

    NSObject provides a basic implementation of automatic key-value change notification. Automatic key-value change notification informs observers of changes made using key-value compliant accessors, as well as the key-value coding methods. Automatic notification is also supported by the collection proxy objects returned by, for example, mutableArrayValueForKey:.
    我们需要用mutableArrayValueForKey来修改集合的数据

        [[self.person mutableArrayValueForKey:@"hobbyArray"] addObject:@"footBall"];
    //打印日志
    {
        indexes = "<_NSCachedIndexSet: 0x600000c02920>[number of indexes: 1 (in 1 ranges), indexes: (3)]";
        kind = 2;
        new =     (
            footBall
        );
    }
    

    我们看到 kind = 2,在前面我们看到kind = 1,kind到底是何方神圣呢?

    typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
        NSKeyValueChangeSetting = 1,
        NSKeyValueChangeInsertion = 2,
        NSKeyValueChangeRemoval = 3,
        NSKeyValueChangeReplacement = 4,
    };
    

    我们触发让kind等于3或者等于4呢?NSKeyValueChangeRemoval我们看到removal应该是移除,NSKeyValueChangeReplacement应该是更换

    2.3.1从数组中移除元素

    移除元素之前,我们先确定数组中有元素所以代码如下

        self.person.hobbyArray = [@[@"footBall",@"basketBall",@"pingpngBall"] mutableCopy];
    //触发观察
        [[self.person mutableArrayValueForKey:@"hobbyArray"] removeLastObject];
    打印日志
    {
        indexes = "<_NSCachedIndexSet: 0x600000c30ba0>[number of indexes: 1 (in 1 ranges), indexes: (2)]";
        kind = 3;
    }
    

    我们心心念念的kind = 3出现了。kind = 4呢?

        [[self.person mutableArrayValueForKey:@"hobbyArray"] replaceObjectAtIndex:0 withObject:@"billiards"];
    //打印日志
    {
        indexes = "<_NSCachedIndexSet: 0x6000032d8e40>[number of indexes: 1 (in 1 ranges), indexes: (0)]";
        kind = 4;
        new =     (
            billiards
        );
    }
    

    3.KVO原理

    3.1.KVO观察的是属性还是成员变量

    我们在PWPerson中添加一个公开的成员变量

    @interface PWPerson : NSObject
    {
        @public
        NSString *name;
    }
    
    @property (nonatomic, copy) NSString *name;
    
    @end
        [self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
        self.person.name = @"pw";
        self.person->name = @"name";
    

    打印日志

    {
        kind = 1;
        new = pw;
    }
    

    KVO观察的是属性

    3.1.谣传KVO会生成中间类

       NSLog(@"----%@",NSStringFromClass(object_getClass(self.person)));
        [self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
        NSLog(@"----%@",NSStringFromClass(object_getClass(self.person)));
    

    打印日志

    ----PWPerson
    ----NSKVONotifying_PWPerson
    

    3.1.1查看NSKVONotifying_PWPerson的父类

        NSString *className = @"NSKVONotifying_PWPerson";
        Class  newClass = NSClassFromString(className);
        if (newClass) {
            
            while (newClass) {
                NSLog(@"%@",NSStringFromClass(newClass));
                newClass = class_getSuperclass(newClass);
            }
        }
    

    打印日志

    NSKVONotifying_PWPerson
    PWPerson
    NSObject
    

    3.1.1查看NSKVONotifying_PWPerson的方法列表

        NSString *className = @"NSKVONotifying_PWPerson";
        Class  newClass = NSClassFromString(className);
       unsigned  int count;
        Method *methods = class_copyMethodList(newClass, &count);
        for (int i = 0; i< count; i++) {
            Method method = methods[i];
            SEL sel= method_getName(method);
            NSLog(@"%@",NSStringFromSelector(sel));
            
        }
    
    

    打印日志

    setName:
    class
    dealloc
     _isKVOA
    

    我们对PWPerson对象进行[self.person class],结果是PWPerson,重写了class方法
    setName方法的目的可能就是向观察者发送消息,当值发生变化是发送消息
    _isKVOA可能标记这个类是kvo生成的中间类。
    dealloc:对观察者释放。

    4.KVO简单的自定义

    1:动态生成子类:NSKVONotifiy_A

    1:防止实例变量的影响,我添加断言异常排除
    2:动态子类的过程: A:生成类 B:添加Class方法 C:注册
    3.同时也判断类的存在性做了处理

    2.动态给子类添加Setter方法

    3.消息转发给父类(runtime消息转发)

    具体实现可以参看KVOController

    相关文章

      网友评论

        本文标题:iOS-OC底层18:KV0原理

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