美文网首页
iOS开发 KVO探寻

iOS开发 KVO探寻

作者: pipiOU | 来源:发表于2019-11-27 20:36 被阅读0次

    目录
    1.什么是KVO?
    2.KVO实现原理
    3.通过KVC设置value值KVO能否生效?
    4.通过成员变量直接赋值KVO能否生效?

    1.什么是KVO

    KVO (Key-Value Observing)是 Objective-C对观察者设计模式的一种实现,
    KVO 提供一种机制,指定一个被观察对象(例如 A 类),当对象某个属性发生更改时,观察对象会获得通知,并做一些业务处理.

    2.KVO实现原理

    Apple官方文档关于KVO的说明:

    Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ...

    大意就是:KVO是使用isa混写技术(isa-swizzling)实现的,被观察对象的 isa 指针会指向一个中间类,而不是原来真正的类.

    简单概述 KVO 的实现

    1.当我们观察一个对象A时,KVO机制会动态创建一个新的类NSKVONotifying_A.

    2.在这个过程,被观察对象的 isa 指针从指向原来的 A 类,被 KVO 机制修改为指向系统新创建的子类 NSKVONotifying_A 类,来实现当前类属性值改变的监听.

    3.NSKVONotifying_A 类重写了被观察属性的 setter 方法,重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象值的更改。

    4.这个中间类,继承自原本的那个类。

    KVO原理演示
    代码演示
    "OYObject.h"文件
    #import <Foundation/Foundation.h>
    
    @interface OYObject : NSObject
    @property (nonatomic, assign) int value;
    - (void)TestChange;
    @end
    

    "OYObject.m"文件

    #import "OYObject.h"
    
    @implementation OYObject
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            _value = 0;
        }
        return self;
    }
    
    - (void)TestChange{
        _value = 5;//直接为成员变量赋值
    }
    @end
    
    

    "OYObserver.h"/"OYObserver.m"文件

    //OYObserver.h文件
    #import <Foundation/Foundation.h>
    
    @interface OYObserver : NSObject
    
    @end
    
    //OYObserver.m文件
    #import "OYObserver.h"
    #import "OYObject.h"
    
    @implementation OYObserver
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        if ([object isKindOfClass:[OYObject class]] && [keyPath isEqualToString:@"value"]) {
            //获取value新值
            NSNumber *valueNum = [change valueForKey:NSKeyValueChangeNewKey];
            NSLog(@"value 新值:%@",valueNum);
        }
    }
    
    @end
    

    ViewController中:

    #import "ViewController.h"
    #import "OYObject.h"
    #import "OYObserver.h"
    @interface ViewController ()
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        OYObject *obj = [[OYObject alloc] init];
        OYObserver *observer = [[OYObserver alloc] init];
        //调用kvo方法监听obj的value属性的变化
        [obj addObserver:observer forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:nil];
        //通过setter方法修改value
        obj.value = 1;
    }
    @end
    
    

    设置如下图两个断点
    运行后,在LLDB中,po命令:po object_getClassName(obj) 查看此时obj所属的类,如下图,此时obj是OYObject类的实例对象.

    往下执行一步:po命令:po object_getClassName(obj) 发现此时obj已经变为NSKVONotifying_OYObject的实例对象了,这就验证了上面所说的KVO会动态创建一个中间类NSKVONotifying_OYObject,并且obj的isa指针指向了它

    3.通过KVC设置value值KVO能否生效?

    #import "ViewController.h"
    #import "OYObject.h"
    #import "OYObserver.h"
    @interface ViewController ()
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        OYObject *obj = [[OYObject alloc] init];
        OYObserver *observer = [[OYObserver alloc] init];
        //调用kvo方法监听obj的value属性的变化
        [obj addObserver:observer forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:nil];
        //通过setter方法修改value
        obj.value = 1;
        //通过kvc设置value能否生效
        [obj setValue:@2 forKey:@"value"];
    }
    //不要忘了在dealloc中移除observer,这里就不写了
    @end
    

    输出:

    Runtime-study[17800:14302974] value 新值:1
    Runtime-study[17800:14302974] value 新值:2

    为什么通过kvc设置value可以让KVO生效?
    因为KVC 会调用obj的setter方法

    4.通过成员变量直接赋值KVO能否生效?

    ViewController中调用 给成员变量_value赋值的TestChange方法:

    #import "ViewController.h"
    #import "OYObject.h"
    #import "OYObserver.h"
    @interface ViewController ()
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        OYObject *obj = [[OYObject alloc] init];
        OYObserver *observer = [[OYObserver alloc] init];
        //调用kvo方法监听obj的value属性的变化
        [obj addObserver:observer forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:nil];
        //通过setter方法修改value
        obj.value = 1;
    
        //通过成员变量直接赋值value能否生效?
        [obj TestChange];
    }
    //不要忘了在dealloc中移除observer,这里就不写了
    @end
    

    输出:

    Runtime-study[17800:14302974] value 新值:1

    通过调用 直接给成员变量_value赋值的TestChange方法,发现KVO并没有生效.
    所以直接修改属性对应的成员变量,是不会触发KVO机制的.

    再来看下面的代码,修改 直接给成员变量赋值的方法:

    #import "OYObject.h"
    
    @implementation OYObject
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            _value = 0;
        }
        return self;
    }
    
    - (void)TestChange{
        [self willChangeValueForKey:@"value"];
        _value = 5;//直接为成员变量赋值
        [self didChangeValueForKey:@"value"];
    }
    @end
    
    

    运行,输出:

    Runtime-study[17800:14302974] value 新值:1
    Runtime-study[17800:14302974] value 新值:5

    这时我们发现,直接给成员变量赋值KVO生效了。

    由此可以推知,在NSKVONotifying_OYObject 类重写被观察属性的setter方法时:

    1.被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该 keyPath的属性值即将变更;
    2.当改变发生后,didChangeValueForKey:被调用,通知系统该 keyPath 的属性值已经变更;
    3.之后, observeValueForKey:ofObject:change:context: 也会被调用。且重写观察属性的 setter 方法这种继承方式的注入是在运行时而不是编译时实现的。

    KVO 为子类的观察者属性重写调用存取方法的工作原理在代码中相当于:

    //NSKVONotifying_A的setter实现
    -(void)setValue:(id)obj{
        [self willChangeValueForKey:@"keyPath"];    //KVO 在调用存取方法之前总调用
        [super setValue:newName forKey:obj]; //调用父类的实现,即原类的实现
        [self didChangeValueForKey:@"keyPath"];     //KVO 在调用存取方法之后总调用
    }
    

    总结:
    1.使用setter方法改变值KVO会生效
    2.使用setValue:forKey 改变值KVO会生效
    3.成员变量直接修改值,KVO不会生效,需要手动添加KVO的两个方法才会生效

    参考文章iOS开发 -- KVO的实现原理与具体应用

    相关文章

      网友评论

          本文标题:iOS开发 KVO探寻

          本文链接:https://www.haomeiwen.com/subject/khqmwctx.html