iOS中KVO使用和底层原理

  • 2018-12-17
  • 256
  • 0
  • 0

iOS 中 KVO, 是key-value-observing 的缩写, 是Objective-C 对观察者设计模式的一种实现,类似观察者设计模式的还有NSNotificationCenter,不过一个是一对一(KVO),一个是一对多(NSNotificationCenter) ;

一般继承自NSObject 的对象都支持KVO. 日常开发中我们常常会监听数据模型的变化, 从而达到根据数据模型的修改对视图进行更新的要求;

KVO 常用方法

/*
注册监听器
监听器对象为observer,被监听对象为消息的发送者即方法的调用者在回调函数中会被回传
监听的属性路径为keyPath支持点语法的嵌套
监听类型为options支持按位或来监听多个事件类型
监听上下文context主要用于在多个监听器对象监听相同keyPath时进行区分
添加监听器只会保留监听器对象的地址,不会增加引用,也不会在对象释放后置空,因此需要自己持有监听对象的强引用,该参数也会在回调函数中回传
*/
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

eg: 
[obj addObserver:observer forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:NULL];


/*
删除监听器
监听器对象为observer,被监听对象为消息的发送者即方法的调用者,应与addObserver方法匹配
监听的属性路径为keyPath,应与addObserver方法的keyPath匹配
监听上下文context,应与addObserver方法的context匹配
*/
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;

/*
监听器对象的监听回调方法
keyPath即为监听的属性路径
object为被监听的对象
change保存被监听的值产生的变化
context为监听上下文,由add方法回传
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;



举个栗子 🌰

假如我的一个数据模型(student)的属性(age)发生了变化, 我需要在对应的视图进行修改这个属性的显示;

@property (nonatomic, strong) StudentModel  *student;

- (void)viewDidLoad {
  [super viewDidLoad];

  _student = [[Student alloc] init];

  _student.age = 10;

  [_student addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];

  [self requestData];
}

- (void)requestData{
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    [manager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
       NSInteger age = [(NSDictionary *)responseObject[@"age"] integerValue];
       _student.age = age;
   } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
   }];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context{
  if ([keyPath isEqualToString:@"age"] && object == _student){
        NSLog(@"当前年龄是: %ld",_student.age);
    }

}
- (void)dealloc{

  [_student removeObserver:self forKeyPath:@"age"]; 

}

实现原理 🚀

Apple 使用 isa 混写技术实现 KVO , 当观察对象 testClass的时候, KVO 会动态创建一个继承自testClass的类 NSKVONotifying_testClass 并重写监听属性的 setter 方法,在调用 setter 的前后, 会观察对象属性的更改情况;子类拥有自己的 set 实现,

    [self willChangeValueForKey:@"value"];
    _value = value;
    [self didChangeValueForKey:@"value"];

所以, 通过 set方法进行赋值, 通过 KVC setValue:forKey:两种方式都可以对 KVO 生效;
但是如果想通过对成员变量直接赋值的话, 需要手动添加 KVO 才可以生效

   QKTestObject *obj = [[QKTestObject alloc] init];
    QKTestObserver *observer = QKTestObserver.alloc.init;

//    通过 KVO 监听 obj 的变化

    [obj addObserver:observer forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:NULL];

//    通过 setter 修改 value, 监听生效
//    obj.value = 2;

//    通过 kvo 修改 value , 监听生效
//    [obj setValue:@4 forKey:@"value"];


//    通过成员变量赋值. 未执行监听方法中的输出
    /*
        成员变量需要手动 添加 KVO , 监听才会生效
         [self willChangeValueForKey:@"value"];
         _value += 1;
         [self didChangeValueForKey:@"value"];
     */
    [obj changeValue];


代码地址:

评论

还没有任何评论,你来说两句吧

你必须 登录 才能发表评论.