美文网首页
KVC是怎么访问属性的?KVO怎么实现的?

KVC是怎么访问属性的?KVO怎么实现的?

作者: test158888 | 来源:发表于2017-07-20 16:44 被阅读69次

    KVC是怎么访问属性的

    • KVC在某种程度上提供了替代存取方法(访问器方法)的方案,不过存取方法终究是个好东西,以至于只要有可能,KVC也尽可能先尝试使用存取方法访问属性。当使用KVC访问属性时,它内部其实做了很多事:
    • 首先查找有无<property>,set<property>,is<property>等property属性对应的存取方法,若有,则直接使用这些方法;
    • 若无,则继续查找<property>,_get<property>,_set<property>等方法,若有就使用;
    • 若查询不到以上任何存取方法,则尝试直接访问实例变量<property>,<property>;
    • 若连该成员变量也访问不到,则会在下面方法中抛出异常。之所以提供这两个方法,valueForUndefinedKey:和setValue:forUndefinedKey:方法,,就是让你在因访问不到该属性而程序即将崩掉前,供你重写,在内做些处理,防止程序直接崩掉。

    KVO是什么?

    • Key-Value Obersver,即键值观察。它是观察者模式的一种衍生。基本思想是,对目标对象的某属性添加观察,当该属性发生变化时,会自动的通知观察者。这里所谓的通知是触发观察者对象实现的KVO的接口方法。KVO是解决model和view同步的好法子。另外,KVO的优点是当被观察的属性值改变时是会自动发送通知的,这比通知中心需要post通知来说,简单了许多。

    • 首先给目标对象的属性添加观察:

     - (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<NSString*, id> *)change context:(nullable void *)context;
    
    • 最后要移除观察者:
    - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
    

    KVO是怎么实现的?

    当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中被观察属性的 setter 方法,在setter方法里使其具有通知机制。因此,要想KVO生效,必须直接或间接的通过setter方法访问属性(KVC的setValue就是间接方式)。直接访问成员变量KVO是不生效的。

    同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。
    重新的setter方法里到底干了什么,而使其就有了通知机制呢?其实只是在setter方法里,给属性赋值的前后分别调用了两个方法

    - (void)willChangeValueForKey:(NSString *)key;
    - (void)didChangeValueForKey:(NSString *)key;
    而- (void)didChangeValueForKey:(NSString *)key;会调用
    
    - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context;
    

    这就是KVO实现的基本原理了!
    当没有存取方法而通过KVC的setValue修改属性值时,同样的在运行时也会在setValue:forKey方法里默认调用上面俩方法。
    其实我们也可以手动,显式的调用这两个方法,以使其具有通知机制。
    下面用例子验证:

    #import "ViewController.h"
    
    @interface ViewController ()
    {
        NSString            *_testStr;
    }
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        // 给self的添加self观察者,自己观察自己的testStr成员变量
        [self addObserver:self forKeyPath:@"testStr" options:NSKeyValueObservingOptionNew context:nil];
    
        [self willChangeValueForKey:@"testStr"];
        _testStr = @"this is a test"; // 直接修改成员变量的值,但是显式的、手动的调用上下俩方法,使其就有通知机制
        [self didChangeValueForKey:@"testStr"];
    }
    
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
    {
        if(object == self && [keyPath isEqualToString:@"testStr"])
        {
            NSLog(@"----new:%@----",change[@"new"]);
        }else
        {
            [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        }
    }
    
    
    - (void)dealloc
    {
        // 移除观察者
        [self removeObserver:self forKeyPath:@"stuName"];
    }
    
    @end
    

    相关文章

      网友评论

          本文标题:KVC是怎么访问属性的?KVO怎么实现的?

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