iOS kvo kvc

iOS kvo kvc

KVC (键值编码)

Objective-C还支持一种更灵活的操作方式,这种方式允许以字符串形式间接操作对象的属性,这种方式的全称是Key Value Coding(简称KVC),即键值编码。

最基本的KVC由NSKeyValueCoding协议提供支持,最基本的操作属性的两个方法如下:

  • setValue:属性值forKey:属性名:为指定属性设置值。
  • valueForKey:属性名:获取指定属性的值。

用法如下:
@property (nonatomic, copy) NSString *name;
设定:
[object setValue:@"Suxiaoyao" forKey:@"name"]
取值:
NSString *nameStr = [object valueForKey:@"name"]

无论是setValue:属性值forKey:还是valueForKey:属性名:,它们的实现逻辑都是差不多的:

1.首先会从调用方法的类中寻找setter或getter方法,找到就调用,否则下一步

2.然后从调用对象中找_name成员变量,找到赋值,否则下一步

3.从调用对象中找name成员变量,找到赋值,否则下一步

4.调用setValue: forUndefinedKey:方法或valueForUndefinedKey:方法,默认会引发一个异常,将会导致程序奔溃 ;

(为了避免奔溃,我们往往要重写setValue: forUndefinedKey:方法或valueForUndefinedKey:方法,这样的话,当KVC操作并不存在的key时,KVC机制总是会调用重写的方法进行处理,通过这种处理机制,可以非常方便的定制自己的处理行为。)

处理nil值

当调用KVC来设置对象的属性时,如果属性的类型是对象类型(如NSString),尝试将属性设置为nil,是合法的,程序可以正常运行。

但是如果属性的类型是基本类型(如int、float、double),尝试将属性设置为nil,程序将会崩溃引发以下异常**‘NSInvalidArgumentException’,并且从提示信息可以知道setNilValueForKey:**方法导致了这个异常。

这时可以重写setNilValueForKey ;

Key Path

当我们访问对象属性的属性时,就会用到key路径;

假设 person 对象有属性 address,address 有属性 city,我们可以这样通过 person 来访问 city:

[person valueForKeyPath:@“address.city”];

KVC协议中为操作Key路径的方法如下:

  • setValue:forKeyPath: 根据Key路径设置属性值
  • valueForKeyPath: 根据Key路径获取属性值

同时key路径对集合操作也支持:

如下:

NSArray *a = @[@4, @84, @2];
NSLog(@"max = %@", [a valueForKeyPath:@"@max.self"]);

我们有一个 Transaction 对象的数组,对象有属性 amount 的话,我们可以这样获得最大的 amount:

NSArray *a = @[transaction1, transaction2, transaction3];
NSLog(@"max = %@", [a valueForKeyPath:@"@max.amount"]);

KVC的优点

使用KVC编程的优势是更加简洁,更适合提炼一些通用性质的代码。由于KVC允许通过字符串形式来操作对象的属性,这个字符串既可是常量,也可是变量,因此具有极高的灵活性。

KVO (键值监听)

如果程序存在的需求是:在数据模型组件的状态数据发生改变时,视图组件能动态地更新自己,及时显示数据模型组件更新后的数据。

KVO机制NSKeyValueObserving协议提供支持,当然,NSObject遵守了该协议,因此,NSObject的子类都可使用该协议中的方法,该协议包含如下常用的方法可用于注册监听器:

  • addObserver:forKeyPath:options:context: 注册一个监听器用于监听指定Key路径

  • removeObserver:forKeyPath: 为指定Key路径删除指定的监听器

  • removeObserver:forKeyPath:context: 为指定Key路径删除指定的监听器,只是多了一个context参数。

KVO编程的步骤如下:

  • 为被监听对象(通常是数据模型组件)注册监听器
  • 重写监听器的observeValueForKeyPath:ofObject:change:context:方法
  • 移除监听器
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.person = [[person alloc] init] ;
    [self.person setValue:@"hello" forKey:@"name"] ;
    self.view.backgroundColor = UIColor.whiteColor ;
    UIButton* btn = [UIButton buttonWithType:UIButtonTypeCustom] ;
    btn.frame = CGRectMake(30, 30, 60, 60) ;
    btn.backgroundColor = UIColor.redColor ;
    [btn addTarget:self action:@selector(pressbtn) forControlEvents:UIControlEventTouchUpInside] ;
    [self.view addSubview:btn] ;
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil] ;
}

- (void)pressbtn {
    [self.person setValue:@"hhh" forKey:@"name"] ;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"name"]) {
        NSLog(@"name has change") ;
        NSLog(@"%@",self.person.name) ;
    }
}

[self.person addObserver:self forKeyPath:@“name” options:NSKeyValueObservingOptionNew context:nil] ;

这里的self.person是被观察者self为观察者,观察者监听被观察者对应keypath的成员变量,变化就通知观察者调用其- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { ;

KVO的优点

KVO这种编码方式使用起来很简单,很适用于model修改后,引发的view的变化这种情况,就像上边的例子那样,当更改属性的值后,监听对象会立即得到通知。

KVO的实现原理

KVO的全称是Key-Value Observing,俗称"键值监听",可以用于监听摸个对象属性值得改变。

img

首先从一个问题入手,如果不通过setter方法修改被监听的属性值,KVO监听还会生效吗 ?

实际上这么问了,肯定不会生效了;具体原因在于它的实现原理;

打印两个实例对象person1和person2的isa指针指向的类对象,假设person1添加了KVO监听,person2没有 ;

这个时候会发现person1的isa指针打印出的是: NSKVONotifying_DLPerson而person2的isa指针打印出来的是: DLPerson

所以说,如果对象没有添加KVO监听那么它的isa指向的就是自己原来的类对象,如下图

未使用KVO监听的对象

当一个对象添加了KVO的监听时,当前对象的isa指针指向的就不是你原来的类,指向的是另外一个类对象,如下图

使用了KVO监听的对象

  • NSKVONotifying_DLPerson类是 Runtime动态创建的一个类,在程序运行的过程中产生的一个新的类。
  • NSKVONotifying_DLPerson类是DLPerson的一个子类。
  • NSKVONotifying_DLPerson类存在自己的 setAge:、class、dealloc、isKVOA…方法。

在明确NSKVONotifying_DLPerson类是DLPerson的一个子类的情况下,当修改值的时候,大概会执行下面伪代码中的setter方法:

///> NSKVONotifying_DLPerson.m 文件

#import "NSKVONotifying_DLPerson.h"

@implementation NSKVONotifying_DLPerson

- (void)setAge:(int)age{
  _NSSetIntValueAndNotify();  ///> 文章末尾 知识点补充小结有此方法来源
}

void _NSSetIntValueAndNotify(){
  [self willChangeValueForKey:@"age"];
  [super setAge:age];
  [self didChangeValueForKey:@"age"];
}

- (void)didChangeValueForKey:(NSString *)key{
  ///> 通知监听器 key发生了改变
  [observe observeValueForKeyPath:key ofObject:self change:nil context:nil];
}

@end

KVO调用顺序

1.修改值的时候,调用NSKVONotifying_DLPerson类的setter方法 ;

2.在这个setter方法中会按顺序执行下面三个方法

  • 调用willChangeValueForKey:
  • 调用原来的setter实现
  • 调用didChangeValueForKey:(这里执行监听通知的回调)

KVC的实现原理

setValue:forKey:的原理

setValue:forKey:的原理

调用setValue:forKey:时

  • 首先会查找setKey:、_setKey: (按顺序查找),有直接调用,没有,先查看accessInstanceVariablesDirectly方法

    + (BOOL)accessInstanceVariablesDirectly{
          return YES;   ///> 可以直接访问成员变量
      //    return NO;  ///>  不可以直接访问成员变量,  
      ///> 直接访问会报NSUnkonwKeyException错误  
      }
    
  • 如果可以访问会按照 _key、_isKey、key、iskey的顺序查找成员变量,找到直接复制,未找到报错NSUnkonwKeyException错误

valueForKey:的原理

额,基本同上

一些面试题补充

iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)

 利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
 willChangeValueForKey:
 父类原来的setter
 didChangeValueForKey:
 内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:

如何手动触发KVO?

 手动调用willChangeValueForKey:和didChangeValueForKey:

直接修改成员变量会触发KVO么?

 不会触发KVO,因为直接修改成员变量并没有走set方法。

通过KVC修改属性会触发KVO么?

看情况,看有没有涉及setter方法的调用

KVC的赋值和取值过程是怎样的?原理是什么?

看上面的流程图
  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值