iOS KVO(NSKeyValueObserving,Key-Value Observing)

返回上级目录:iOS KVC-KVO-Runtime

1.定义

KVO (Key-Value Observing) 键值观察,是iOS中,对象采用的一种非正式协议,用于通知其他对象的指定属性的更改。

KVO是通过NSObject的一个分类NSKeyValueObserving来实现的,所以所有继承自NSObject的类都可以使用KVO,不是继承自NSObject就不具备KVO的功能(如结构体或是一些纯swift的类)

苹果文档的定义是:An informal protocol that objects adopt to be notified of changes to the specified properties of other objects. 如下图:

苹果官网指导链接:Key-Value Observing Programming Guide

在这里插入图片描述
鼠标放在NSObject类名上,点击option会弹出文档
在这里插入图片描述
在这里插入图片描述

2.主要方法,和代码示例

注意点

KVO的addObserver和removeObserver需要是成对的,如果重复remove则会导致NSRangeException类型的Crash,如果忘记remove则会在观察者释放后再次接收到KVO回调时Crash。

苹果官方推荐的方式是,在init的时候进行addObserver,在dealloc时removeObserver,这样可以保证add和remove是成对出现的,是一种比较理想的使用方式。

添加观察者

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

实现属性值改变时会调用的方法

- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;

移除观察者

- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

示例代码:

#import "ViewController.h"
#import "Person.h"

@interface ViewController ()

@property (strong, nonatomic) IBOutlet UILabel *label;

@property(strong,nonatomic)Person *person;

@end

@implementation ViewController

- (IBAction)btnClick:(id)sender {
    self.person.name = @"newName";
}


- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.person = [Person new];
    //NSKeyValueObservingOptions
    //NSKeyValueObservingOptionNew:提供更改前的值
    //NSKeyValueObservingOptionOld:提供更改后的值
    //NSKeyValueObservingOptionInitial:观察最初的值(在注册观察服务时会调用一次触发方法)
    //NSKeyValueObservingOptionPrior:分别在值修改前后触发方法(即一次修改有两次触发)
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew |
     NSKeyValueObservingOptionOld context:@"这是什么"];
    self.person.name = @"name";
    self.label.text = self.person.name;
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%@",keyPath);
    NSLog(@"%@", change);
    NSLog(@"%@",object);
    NSLog(@"%@",context);
    NSLog(@"-----------------------------------");
    self.label.text = [change objectForKey:@"new"];
}

- (void)dealloc{
    NSLog(@"dealloc");
    [self.person removeObserver:self forKeyPath:@"name"];
}

@end

打印结果:
2020-07-16 14:39:15.922700+0800 KVO[2852:79289] name
2020-07-16 14:39:15.922924+0800 KVO[2852:79289] {
kind = 1;
new = name;
old = “”;
}
2020-07-16 14:39:15.923065+0800 KVO[2852:79289] <Person: 0x600001360180>
2020-07-16 14:39:15.923190+0800 KVO[2852:79289] 这是什么
2020-07-16 14:39:15.923296+0800 KVO[2852:79289] -----------------------------------
2020-07-16 14:39:18.521655+0800 KVO[2852:79289] name
2020-07-16 14:39:18.521934+0800 KVO[2852:79289] {
kind = 1;
new = newName;
old = name;
}
2020-07-16 14:39:18.522121+0800 KVO[2852:79289] <Person: 0x600001360180>
2020-07-16 14:39:18.522285+0800 KVO[2852:79289] 这是什么
2020-07-16 14:39:18.522424+0800 KVO[2852:79289] -----------------------------------

3.KVO的底层实现原理(isa-swizzling)

iOS 了解isa-swizzling (类指针交换)

如下以Person类为例,Person的实例对象使用KVO,调用addObserver方法后:

1.动态生成了中间类NSKVONotifying_Person, 且NSKVONotifying_Person的父类是Person

2.改写了中间类NSKVONotifying_Person的class方法,使调用class方法的时候返回Person

3.重写NSKVONotifying_Person类name属性的setter方法,使得name值改变时,观察者可以收到通知,observeValueForKeyPath方法被调用

4.重写NSKVONotifying_Person类 dealloc 方法来释放资源

5.重写NSKVONotifying_Person类的_isKVOA方法,用来标示该类是一个 KVO 机制声称的类

    self.person = [Person new];
    NSLog(@"realClass:%@",object_getClass(self.person));
    NSLog(@"class:%@",[Person class]);
    NSLog(@"realSuperClass:%@",class_getSuperclass(object_getClass(self.person)));
    
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew |
     NSKeyValueObservingOptionOld context:nil];
    
    NSLog(@"addObserver后");
    NSLog(@"realClass:%@",object_getClass(self.person));
    NSLog(@"class:%@",[Person class]);
    NSLog(@"realSuperClass:%@",class_getSuperclass(object_getClass(self.person)));

打印结果:
2020-07-16 20:20:31.596967+0800 KVO[5621:195863] realClass:Person
2020-07-16 20:20:31.597122+0800 KVO[5621:195863] class:Person
2020-07-16 20:20:31.597277+0800 KVO[5621:195863] realSuperClass:NSObject
2020-07-16 20:20:31.597678+0800 KVO[5621:195863] addObserver后
2020-07-16 20:20:31.597785+0800 KVO[5621:195863] realClass:NSKVONotifying_Person
2020-07-16 20:20:31.597913+0800 KVO[5621:195863] class:Person
2020-07-16 20:20:31.598015+0800 KVO[5621:195863] realSuperClass:Person

从上面代码的打印可以看出,调用addObserver方法,使用KVO的时候,系统生成了一个中间类NSKVONotifying_Person,而且这个中间类继承自Person.

当自己手动添加一个NSKVONotifying_Person类时,会导致KVO失效,报:

[general] KVO failed to allocate class pair for name NSKVONotifying_Person, automatic key-value observing will not work for this class

如下图:

在这里插入图片描述

如果在observeValueForKeyPath方法里打断点,沿着断点一步步向前调试,如下图:
在这里插入图片描述
会发现从name值改变的代码到observeValueForKeyPath方法被调用,经历了如下方法
在这里插入图片描述
猜测NSKVONotifying_Person类,setName方法的实现可能类似下面的oc代码:

- (void)setName:(NSString *)name{
    _NSSetObjectValueAndNotify();
}

void _NSSetObjectValueAndNotify {
    [self willChangeValueForKey:@"name"];
    [super setName:name];
    [self didChangeValueForKey:@"name"];
}

- (void)didChangeValueForKey:(NSString *)key{
    [observe observeValueForKeyPath:key ofObject:self change:nil context:nil];
}

用runtime,打印一下Person和NSKVONotifying_Person的方法

    self.person = [Person new];
    [self getMethod];
    NSLog(@"--------------");
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew |
     NSKeyValueObservingOptionOld  context:nil];
    [self getMethod];
- (void)getMethod {
    unsigned int count;
    Method *methods = class_copyMethodList(object_getClass(self.person), &count);
        
    for (NSInteger index = 0; index < count; index++) {
       Method method = methods[index];
       NSString *methodStr = NSStringFromSelector(method_getName(method));
       NSLog(@"%@\n", methodStr);
    }
}

打印结果
2020-07-17 17:44:03.989443+0800 KVO[4100:137298] .cxx_destruct
2020-07-17 17:44:03.989644+0800 KVO[4100:137298] name
2020-07-17 17:44:03.989781+0800 KVO[4100:137298] setName:
2020-07-17 17:44:03.989902+0800 KVO[4100:137298] --------------
2020-07-17 17:44:08.158801+0800 KVO[4100:137298] setName:
2020-07-17 17:44:08.158995+0800 KVO[4100:137298] class
2020-07-17 17:44:08.159136+0800 KVO[4100:137298] dealloc
2020-07-17 17:44:08.159311+0800 KVO[4100:137298] _isKVOA

发现NSKVONotifying_Person重写了4个方法,重写这些方法的作用:

setName:在属性改变的时候通知观察者,调用observeValueForKeyPath方法
class:重写这个方法,是为了伪装苹果自动为我们生成的中间类。
dealloc:应该是处理对象销毁之前的一些收尾工作
_isKVOA:告诉系统使用了kvo

4.动态生成一个类,仿写KVO

自己给NSObject写一个kvo的分类

NSObject+kvo.h

@interface NSObject (kvo)

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

@end

NSObject+kvo.m

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

@implementation NSObject (kvo)

- (void)xy_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
    //动态添加一个类
    NSString *originClassName = NSStringFromClass([self class]);
    NSString *newClassName = [@"XYKVO_" stringByAppendingString:originClassName];
    const char *newName = [newClassName UTF8String];
    //动态添加的类为当前类的子类
    Class kvoClass = objc_allocateClassPair([self class], newName, 0);
    //添加setter方法
    class_addMethod(kvoClass, @selector(setName:), (IMP)setName, "v@:@");
    //注册新添加的这个类
    objc_registerClassPair(kvoClass);
    //修改isa指针,由xx指向XYKVO_xx
    object_setClass(self,kvoClass);
    //保存观察者到当前实例对象中
    objc_setAssociatedObject(self, (__bridge const void *)@"observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}


#pragma mark - 重写父类方法

//给动态的类添加的setter方法
void setName(id self, SEL _cmd, NSString *name) {
    //保存动态生成的KVO类
    Class kvoClass = [self class];
    //将isa指针指回父类xx
    object_setClass(self, class_getSuperclass([self class]));
    //调用父类的setter方法
    objc_msgSend(self, @selector(setName:), name);
    //取出实例对象的观察者
    id objc = objc_getAssociatedObject(self, (__bridge const void *)@"observer");
    //通知观察者,执行通知方法
    objc_msgSend(objc, @selector(observeValueForKeyPath:ofObject:change:context:),name,self,nil,name);
    //重新修改为XYKVO_xx类
    object_setClass(self, kvoClass);
}

@end

使用自己写的kvo分类
在这里插入图片描述

参考博客

KVO讲解及实现

iOS KVC和KVO详解
KVO原理分析及使用进阶(FBKVOController)
分析实现-实现KVO(YYModel)

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页