KVO 使用及原理

Posted Da雪山

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KVO 使用及原理相关的知识,希望对你有一定的参考价值。

KVO的基本原理大概是这样的

  当一个对象被观察时, 系统会新建一个子类NSNotifying_A ,在子类中重写了对象被观察属性的 set方法,  并且改变了该对象的 isa 指针的指向(指向了新建的子类) , 当属性的值发生改变了, 会调用子类的set方法, 然后发出通知

一. KVO 的基本使用

给_person对象 添加观察者self, 当person对象的name的值发生改变的时候, 会触发observer方法

_person = [Person new]; p.name = @"oldName"; 
//添加观察者
// [p addObserver:self forKeyPath:@"dog" options:NSKeyValueObservingOptionNew context:nil];
[_person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];

// 所观察的对象的keyPath 发生改变的时候, 会触发
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%@",keyPath);
    NSLog(@"%@",change);
    
    
}

 

二.  当keyPath 为对象时, 改对象有许多属性, 怎么办?

 在person类中,重写这个方法, 设置需要观察的属性 , 注意:"_dog.level"

//返回一个容器, 里面放的是NSString类型
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
    
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    //观察dog对象中的所有属性
    if ([key isEqualToString:@"dog"]) {
        keyPaths = [keyPaths setByAddingObjectsFromArray:@[@"_dog.level",@"_dog.age"]];
    }
     
    return keyPaths;
}

 

三. 手动触发KVO

系统默认该对象的所有属性 都能被观察到 ,重写下面方法, 可以单独设置某个属性不能被观察

//默认 yes, 默认自动观察所有属性
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
    return YES;
}
//返回NO, 则不能被默认观察到name
+ (BOOL)automaticallyNotifiesObserversOfName{ return NO; } + (BOOL)automaticallyNotifiesObserversOfAge{ return YES; }

当 name 发生改变的时, 手动触发, 发送通知

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //手动发通知
    //即将改变(发一次通知)
    [_person willChangeValueForKey:@"name"];
     _person.name = @"dddd";
     //已经改变(发一次通知),一共发了两次通知
    [_person didChangeValueForKey:@"name"];
}

 

四. 自定义KVO

根据kvo的原理, 可以自定义一个kvo, 建一个NSObject的分类, 添加方法

 

@interface NSObject (XSKVO)

- (void)xs_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;

@end

 

通过runtime的方式, 动态创建一个类, 并给该类添加方法

#import "NSObject+XSKVO.h"
 #import <objc/runtime.h>

@implementation NSObject (XSKVO)

- (void)xs_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
    
    //1.新建一个类
    NSString *className = [@"XSKVO" stringByAppendingString: NSStringFromClass([self class])];
    Class newClass = objc_allocateClassPair([self class], className.UTF8String, 0);
     //注册类
    objc_registerClassPair(newClass);
    //2.修改 调用者类型
    object_setClass(self, newClass);
    
    //3.给子类添加set方法(子类里面没有set方法的)
    //OC方法:方法编号SEL ,方法实现IMP
    class_addMethod(newClass, @selector(setName:), xssetName, "");
   
}

/*
 隐藏的参数:
 self  方法的调用者
 _cmd  方法的编号
 
 */
void xssetName(id self,SEL _cmd, NSString *newName){
    NSLog(@"自定义的实现%@",newName);
    //方案一:通过消息机制 发送消息 -observeValueForKeyPath
    
    
}

@end

 

五. 其他

关于容器类(如:NSMutableArray)的观察, 当通过addObject: 向数组中添加对象, 不会触发KVO, 因为并没有触发set方法,

解决方法: 通过KVC 方法 - mutableArrayValueForKey:

 

以上是关于KVO 使用及原理的主要内容,如果未能解决你的问题,请参考以下文章

iOS--KVO的实现原理与具体应用

刨根问底KVO原理

KVC, KVO实现原理

ios KVO的实现原理

使用Runtime自定义KVO,原理浅析

iOS底层探索之KVO—KVO原理分析