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";

被监听类中实现的代码code

// 这个方法不重写,就是默认返回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];
    }
}

移除监听orm

懒得写

KVO原理

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

/** 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函数ci

/** 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的原理rem

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
相关文章
相关标签/搜索