KVO/KVC

作者: YellowTag | 来源:发表于2019-05-02 11:28 被阅读0次

KVO

key-value-observer 观察者模式
通俗来解释就是说,我们监听某个对象的属性,如果发生了变化,则由该对象通知观察者

这几篇文章可以一起食用:
https://www.jianshu.com/p/d0032fd9397b
https://www.jianshu.com/p/5e85e9daaa44
https://www.jianshu.com/p/badf5cac0130

使用教程

1.注册对象

在我们想要监听的对象中调用方法:

[boss addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"123"];
  • 只要是NSObject,都会有这个方法,尽管调用就好了
  • addObserver:由谁来监听这个对象,这个地方是self,那么这个类就应该实现稍后要说的一个方法
  • forKeyPath: 监听的属性名,给一个这个对象监听的属性名字,没有对应的会崩溃
  • options: 参数的选项,这个地方现在不理解没关系,现在的参数设置就是说监听者会记录原来的值和改变后的值
  • context:一个携带信息,如果一个对象的多个属性被监听,可以用这个来区分

2.实现回调函数

当我们监听的对象发生变化之后,会调用一个函数

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
image.png

下面是测试:


image.png

从测试中我们就能看出,change这个字典中保存的是什么键值对,这和前面的option参数设置有关,我设置了oldnew,意味着change这个字典会保存新值和旧值

KVO原理

简单的来概括原理其实就是,被监听的对象有了一个新的类,这个类中重写了setter方法并且会代替原来的类,而这个setter方法就是调用KVO机制的触发器,当属性被改变时,setter方法就会调用KVO来通知观察者

专业点来讲,KVO使用了isa-swizzling机制

  • isa: 每个实例对象都有一个指针来指向自己的原生类,叫做isa

简单来分析过程就是:
1.系统会为被监听的对象会生成一个新的类,这个类的名字以NSNotifying_为前缀
2.这个类重写了setter和getter方法,至于怎么样写的,等会我们有模拟
3.被监听的对象的isa会指向这个新生成的类

这张图很经典


image.png

下面是验证:


image.png
image.png

手动KVO

既然我们知道KVO的原理是重写了setter方法,那么我们可以试着模拟一下,下面两个方法是很重要

 [self willChangeValueForKey:@"name"];
 [self didChangeValueForKey:@"name"];
image.png

下面是测试:


image.png

注意:应该会调用两次回调函数,因为will和did进行了两次通知,上面的测试注释掉了will

关闭KVO

+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@"age"]) {
        return NO;
    }

    return [super automaticallyNotifiesObserversForKey:key];
}

这样子可以让系统自动关闭对这个属性的KVO,但是没事还是不要手动去实现😒

KVO的注意事项

  • KVO只有调用了setter方法才能触发,所以直接修改成员变量的值是不会触发的

这里只是简单地写了下KVO,还有其他深入的知识以后再更

KVC

Key-Value-Coding 键值编码 简单来解释就是----允许开发者通过键名直接来获取属性的值,不管这个值在对象中有没有被暴露出来,是不是私有的都是可以获取到的

参考:
https://www.jianshu.com/p/b9f020a8b4c9

上面的教程讲的足够详细了,这里就挑重点的来讲

  • 只要是继承了NSObject的对象,都能使用KVC
- (nullable id)valueForKey:(NSString *)key;                          //直接通过Key来取值

- (void)setValue:(nullable id)value forKey:(NSString *)key;          //通过Key来设值

- (nullable id)valueForKeyPath:(NSString *)keyPath;                  //通过KeyPath来取值

- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  //通过KeyPath来设值

上面四种方式就是KVC经常会使用到的方法

1.设值

寻找顺序:
Set<key> -> _key -> _iskey ->key -> iskey

一般而言都是找到第一步就行了,进行第二步寻找之前要满足的条件是:

+ (BOOL)accessInstanceVariablesDirectly{
    return YES;//默认YES
}

直接在对应的类(如果你想通过KVC找某个类中的值,就在这个类)中重写这个方法就行,如果返回的是NO 那么就是不允许直接访问实例变量

如果以上几步出现没有找到的情况,会直接(被KVC的类)调用:

- (void)setValue:(id)value forUndefinedKey:(NSString *)key

如果没有重写该方法,默认是抛出异常

如果属性是基本数据类型,比如double,int或者说NSInteger,那么我们在设置的时候要自己做转化,KVC不会自动将数字进行封装,
我们要封装成NSNumber或者NSValue(具体采用哪种视情况而定,基本类型是NSNumber,结构体类型就是NSValue)

[person setValue:[NSNumber numberWithInteger:5] forKey:@"age"];

2.取值

get<Key>,<key>,is<Key>,(后面开始寻找成员变量名) _<key>,_is<Key>,<key>,is<Key>

教程上的 CountofKey方法看不懂 不管先

如果没有找到key那么抛出异常,调用方法:

- (id)valueForKey:(NSString *)key{
    return nil;
}

要注意的是取值的时候,KVC返回的是个id类型的对象,那么对于一些值类型,比如int,在KVC中会先封装成为NSNumber然后在返回一个id对象指向这个NSNumber,所以我们用KVC取得的值总是一个对象.

3.keypath

对于复合型的类,我们可以先取出类中的对象,再用该对象来获取值,但是KVC直接提供keypath的方法来达到同样效果
例子可以查看教程

4.KVC处理异常

  • 如果key是int double等基本类型,则不能传入nil,因为这些值本身就不能为空,不像指针(这一点教程上讲的不清楚)
    如果传入nil,会抛出异常
    - (void)setNilValueForKey:(NSString *)key {
    NSLog(@"不能将%@设成nil", key);
}

5.KVC处理集合

KVC除了能直接操作对象的属性,还提供了一些便利的方法来操作集合

对于NSArray(只能存储对象),那么使用KVC好像也说的过去,归根结底KVC还是整个对数组中每个对象做了处理而已,而是个对象就有key-value

KVC对于NSArray提供的处理函数

@avg, @count , @max , @min ,@sum

看一个简单的例子:

NSArray *numberArray = @[@2,@1,@4,@6,@5,@7,@9,@8];
// 最大值
NSInteger max = [[numberArray valueForKeyPath:@"@max.intValue"] integerValue];
// 最小值
NSInteger min = [[numberArray valueForKeyPath:@"@min.intValue"] integerValue];
// 平均值
NSInteger avg = [[numberArray valueForKeyPath:@"@avg.intValue"] integerValue];
// 总和
NSInteger sum = [[numberArray valueForKeyPath:@"@sum.intValue"] integerValue];

注意 使用的是 valueForKeyPath 我知道可能逻辑上有点看不惯,但是可以先这样用就行
可以再难一点就是数组里面的对象的对象,根据最后一个对象的某个属性获取比如说最大值之类的

对象操作运算

@distinctUnionOfObjects
@unionOfObjects

记住第一个就好了,两者都是返回一个NSArray对象,第一个返回的是去重的结果,第二个没点用
参考:https://www.jianshu.com/p/6b32f6279347

6.KVC处理字典

这一点真的是太方便了

我们可以利用KVC提供的方法将字典转成模型,不需要再一个个属性的赋值,也可以直接从模型转成字典,不需要一个个添加到字典去了


image.png

对于模型转成字典,其实就是将每一个数组的元素对对象用一遍KVC而已,只要理解了KVC这两个就不难了

    Boss *boss=[[Boss alloc] init];
    //模型转成字典
    NSArray *arr=@[@"name",@"money"];
    NSDictionary *dic=[boss dictionaryWithValuesForKeys:arr];
    NSLog(@"%@",dic);
    //字典转成模型
    NSDictionary *dic2=@{@"name":@"小布",@"money":@"0"};
    [boss setValuesForKeysWithDictionary:dic2];
    NSLog(@"%@",[boss valueForKey:@"name"]);
image.png

相关文章

网友评论

      本文标题:KVO/KVC

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