KVO使用及实现原理

KVO使用及实现原理

KVO使用

  • 对属性进行监听
  • 对属性的属性进行监听
  • 容器监听
  • 触发(手动触发,kvc赋值)

添加监听


    // 1.kvo对属性的监听
    [_person addObserver:self forKeyPath:NSStringFromSelector(@selector(name)) options:NSKeyValueObservingOptionNew context:nil];
    // 2.kvo属性关联,这个我们是监听dog的变化,那如果是dog属性的变化,如果不做处理,是监听不到的
    [_person addObserver:self forKeyPath:NSStringFromSelector(@selector(dog)) options:NSKeyValueObservingOptionNew context:nil];
    // 3.容器监听
    [_person addObserver:self forKeyPath:NSStringFromSelector(@selector(mArr)) options:NSKeyValueObservingOptionNew context:nil];

触发代码

    // 触发基本知识点:KVO得通过set方法才可以触发
    
     NSString *name = NSStringFromSelector(@selector(name));
    
    // 手动触发 这两个方法必须是成对的,然后回调observeValueForKeyPath方法,至于为什么成对呢,猜想应该是苹果做了处理,点进去官方的注释也有说明这两个方法必须成对存在
    // 如果想要手动触发需要在被监听类中实现+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key返回NO
    [_person willChangeValueForKey:name];
    [_person didChangeValueForKey:name];
    
    // kvc
    [_person setValue:@"123" forKey:name];
    
    // 容器
    NSMutableArray *arr1 = [_person mutableArrayValueForKey:NSStringFromSelector(@selector(mArr))];
    [arr1 addObject:@"123"];
    
    NSMutableArray *arr2 = [_person mutableArrayValueForKey:NSStringFromSelector(@selector(mArr))];
    [arr2 replaceObjectAtIndex:0 withObject:@"1234"];
    
    NSMutableArray *arr3 = [_person mutableArrayValueForKey:NSStringFromSelector(@selector(mArr))];
    [arr3 removeAllObjects];
    

    
    // 属性关联
    // 属性关联需要在被监听类中实现+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key
    _person.dog.name = @"myDog";

被监听类中实现的代码

// 这个方法不重写,就是默认返回YES,如果重写了返回NO,那么就是需要手动触发了,当然这个可以根据参数key来判断,区分监听的key是手动还是自动 
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
    return YES;
}

// 这个方法用于监听属性的属性的(属性关联)
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
    if ([key isEqualToString:@"dog"]) {
        return [[NSSet alloc] initWithObjects:@"_dog.name", nil];
    } else {
        return [super keyPathsForValuesAffectingValueForKey:key];
    }
}

移除监听

懒得写

KVO原理

1.在运行时的时候创建被监听类的子类

    /** 1.动态生成一个类 */
    /** 1.1 动态生成一个类名 */
    NSString *oldClassName = NSStringFromClass(self.class);
    NSString *newClassName = [@"KVO_" stringByAppendingString:oldClassName];
    /** 定义一个类,继承传进来的类 */
    Class MyClass = objc_allocateClassPair(self.class, newClassName.UTF8String, 0);

2.在子类中重写父类属性的set方法(所以KVO只能监听属性)

    /** 添加set方法 */
    class_addMethod(MyClass, @selector(setName:), (IMP)setName, "V@:@");

3.注册这个子类

    /** 注册该类 */
    objc_registerClassPair(MyClass);

4.修改当前被监听对象的isa指针指向子类

    /** 修改isa指针 */
    object_setClass(self, MyClass);

5.实现set函数


/** set方法 */
void setName(id self,SEL _cmd,NSString * newName){
    /** 保存当前类型 */
    Class class = [self class];
    /** 调用父类方法 */
    object_setClass(self, class_getSuperclass(class));
    objc_msgSend(self, @selector(setName:),newName);
    /** 通知观察者 */
    // 这里有个属性绑定,所以在添加观察者的时候,就是将这个观察者持有(就是使用属性绑定来持有)
    id observer = objc_getAssociatedObject(self, @"objc");
    if (observer) {
        objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),@"name",self,@{@"new:":[self valueForKey:@"name"],@"kind:":@1},nil);
    }
    /** 改回子类 */
     object_setClass(self, class);
     
    // kvo就是在set方法中调用
    willChangeValueForKey:
    didChangeValueForKey:;
    那我们这里需不需要呢,当然不需要,因为我们是自己实现了一套了,调这两句也没有用的
}

思考点

1.为什么kvc可以触发,kvc的原理

2.创建一个子类,如果创建的子类的类名,项目中刚好存在呢

如果项目中存在,那个创建的子类会nil,那么这时候我们可以使用递归创建,为nil就在类名后拼1或者其他字符吧,直到成功为止

3.objc_msgSend(self, @selector(setName:),newName);可以传值,那这个跟performSelector:有什么关系,而且发现直接用objc_msgSend还比performSelector方便

4.为什么oc的每个方法都有两个隐式参数,这两个是哪里来的

oc调用方法是消息机制,表现形式就是
objc_msgSend(self, @selector(setName:),newName);那么每个方法在调用的本质都是使用
objc_msgSend那这个刚好就需要传两个参数,调用者和方法编号,也就是isa和sel

    原文作者:Corbin___
    原文地址: https://segmentfault.com/a/1190000017862501
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞