KVO的用法、底层实现原理

KVO的用法

KVO也就是key-value-observing(即键值观察),利用一个key来找到某个属性并监听其值得改变。用法如下:

  • 添加观察者
  • 在观察者中实现监听方法,observeValueForKeyPath: ofObject: change: context:(通过查阅文档可以知道,绝大多数对象都有这个方法,因为这个方法属于NSObject)
  • 移除观察者
//让对象b监听对象a的name属性
//options属性可以选择是哪个
 /* NSKeyValueObservingOptionNew =0x01, 新值 
  * NSKeyValueObservingOptionOld =0x02, 旧值 
  */ 
[a addObserver:b forKeyPath:@"name"options:kNilOptionscontext:nil]; 
a.name = @"zzz";
#pragma mark - 实现KVO回调方法
/* * 当对象的属性发生改变会调用该方法
    * @param keyPath 监听的属性 
    * @param object 监听的对象 
    * @param change 新值和旧值 
    * @param context 额外的数据 
*/
- (void)observeValueForKeyPath:(NSString *)keyPathofObject:(id)objectchange:(NSDictionary<NSString *,id>*)change context:(void *)context{ 
    NSLog(@"%@的值改变了,",keyPath); 
    NSLog(@"change:%@", change);
}
//最后不要忘记了,去移除observer
- (void)dealloc{ 
       [a removeObserver:b forKeyPath:@"name"];
  }
KVO键值观察者底层解析
涉及到了runtime,关于isa指针
手动实现键值观察(代码示例)

被观察的对象Target(重写setter/getter方法)
Target.h

@interface Target : NSObject
{
   int age;
}
// for manual KVO 
- age- (int) age;
- (void) setAge:(int)theAge;
@end

Target.m

@implementation Target
- (id) init{ 
    self = [super init]; 
    if (nil != self) { 
          age = 10; 
     } 
    return self;
}
// for manual KVO - age
- (int) age{
    return age;
}
- (void) setAge:(int)theAge{ 
    [self willChangeValueForKey:@"age"];
    age = theAge; 
    [self didChangeValueForKey:@"age"];
}
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key { 
    if ([key isEqualToString:@"age"]) {
     return NO;
 } 
return [super automaticallyNotifiesObserversForKey:key];
}
@end

首先,需要手动实现属性的 setter 方法,并在设置操作的前后分别调用 willChangeValueForKey: didChangeValueForKey方法,这两个方法用于通知系统该 key 的属性值即将和已经变更了;
其次,要实现类方法 automaticallyNotifiesObserversForKey,并在其中设置对该 key 不自动发送通知(返回 NO 即可)。这里要注意,对其它非手动实现的 key,要转交给 super 来处理。

实现原理

KVO的实现是基于runtime运行时的,下面就来详细介绍一下原理:还是这张图:


 
  • 当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的 setter 方法。
  • 派生类在被重写的 setter 方法中实现真正的通知机制,就如前面手动实现键值观察那样。这么做是基于设置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。当然前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。
  • 同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。

KVO与Notification之间的区别:

notification是需要一个发送notification的对象,一般是notificationCenter,来通知观察者。

KVO是直接通知到观察对象,并且逻辑非常清晰,实现步骤简单。

原文地址:https://www.cnblogs.com/junhuawang/p/5802325.html