概述
-
KVO(key value observing)
允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。一般继承自NSObject
的对象都支持KVO
-
KVO
和NSNotificationCenter
都是iOS中观察者模式的一种实现,区别如下。- 在于观察者和被观察者之间,KVO是一对一,NSNotificationCenter是一对多的关系。
KVO
对被监听对象无侵入性,不需要修改其内部代码即可实现监听。
基本使用
分三步
-
注册观察者。
addObserver: forKeyPath: options: context:
第一个参数observer:监听者 第二个参数keyPath:要监听的属性 第三个参数options: NSKeyValueObservingOptionNew 新值 NSKeyValueObservingOptionOld 旧值 NSKeyValueObservingOptionInitial 初始值 NSKeyValueObservingOptionPrior 在属性发生更改之前和之后收到两次通知,而不是改变之后才收到一次通知 第四个参数context:传入任意类型的对象,是kvo的一种传值方式
调用此方法后,不会对观察者形成强引用。所以要注意观察者的释放时机,防止出现因观察者被释放导致crash
-
在观察者中实现回调方法。
observeValueForKeyPath: ofObject: change: context:
有多种方式都会触发KVO。
// 点语法 self.car.carName = @"丰田FJ"; // setter方法 [self.car setCarName:@"丰田FJ"]; // setValue: forKey: [self.car setValue:@"丰田FJ" forKey:@"carName"]; // setValue: forKeyPath: [self setValue:@"丰田FJ" forKeyPath:@"car.carName"];
-
移除观察者。
removeObserver: forKeyPath:
-
KVO原理
通过
isa-swizzling
技术实现。 -
在运行时创建一个原类的子类,有固定格式
NSKVONotifying_XXX
-
将对象的
isa
指针指向新创建的类,重写NSKVONotifying_XXX
的-class
方法,返回原类的Class
-
1.为了把流程看的明白一些,我们重写了一下类的- (NSString *)description方法 - (NSString *)description { NSLog(@"obj is %p \n",self); IMP carNameIMP = class_getMethodImplementation(object_getClass(self), @selector(setCarName:)); NSLog(@"IMP setCarName is %p \n",carNameIMP); Class objClass = [self class]; NSLog(@"obj class is %@ \n", objClass); Class objRuntimeClass = object_getClass(self); NSLog(@"obj runtime class is %@ \n", objRuntimeClass); NSLog(@"obj method list is: \n"); unsigned int count; Method *methodList = class_copyMethodList(objRuntimeClass, &count); for (NSInteger i = 0; i < count; i++) { Method method = methodList[i]; NSString *methodName = NSStringFromSelector(method_getName(method)); NSLog(@"method%ld = %@ \n", (long)i, methodName); } NSLog(@"\n"); return @""; }
2. 在添加监听者前后分别调用`description`方法 [self.car description]; // 添加监听者 [self.car addObserver:self forKeyPath:@"carName" options:(NSKeyValueObservingOptionNew) context:@"我想传啥就传啥 nil也行"]; [self.car description]; 3. 打印结果如下 // 注册监听者之前调用的 description obj is 0x281368880 IMP setCarName is 0x1001cbe3c obj class is USTestCar obj runtime class is USTestCar obj method list is: method0 = setCarName: method1 = carName method2 = .cxx_destruct method3 = description // 注册监听者之后调用的 description obj is 0x281368880 IMP setCarName is 0x1f5a46340 // setter方法重写 obj class is USTestCar obj runtime class is NSKVONotifying_USTestCar // 生成的子类 obj method list is: method0 = setCarName: method1 = class method2 = dealloc method3 = _isKVOA // KVO自动生成的子类标识
-
子类重写
setter
方法。在修改值之前调用willChangeValueForKey:
;修改值之后调用didChangeValueForKey
,这两个方法最终都会被调用到observeValueForKeyPath: ofObject: change: context:
这个回调方法中可以自己调用`willChangeValueForKey`和`didChangeValueForKey`。即可在不改变属性值的情况下手动触发KVO。 手动触发KVO 这两个方法必须同时调用。 [self.car willChangeValueForKey:@"carName"]; [self.car didChangeValueForKey:@"carName"];
-
4.最后在- (void)dealloc中移除KVO
-
- (void)dealloc { [self.car removeObserver:self forKeyPath:@"carName"]; [self.car description]; }
打印结果如下:
-
obj is 0x600003260c00 IMP setCarName is 0x10fb9bdb0 obj class is USTestCar obj runtime class is USTestCar obj method list is: method0 = .cxx_destruct method1 = description method2 = setCarName: method3 = carName
衍生出来的NSKVONotifying_USTestCar已经“销毁”,setter方法也都恢复,指针指向了原类。整个KVO的流程结束。