[iOS底层]-KVC底层原理

KVC概述

KVC提供了一种可以间接访问对象属性的方式,即可以通过字符串key来访问某个属性。主要是通过NSKeyValueCoding协议实现。

KVC API介绍

KVC的主要API有4个,两个设置,两个取值。

// 通过key设置值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
-
// 通过key取值
- (nullable id)valueForKey:(NSString *)key;
- (nullable id)valueForKeyPath:(NSString *)keyPath;

forKeyforKeyPath的区别是什么呢?我们通过例子感受一下

// Car 类
@interface Car : NSObject

@property (nonatomic, strong) NSString *brand;

@end

// Person类
@interface Person : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) Car *car;

@end

如果设置Personname属性的值,它们没有任何区别。

Person *person = [[Person alloc] init];

[person setValue:@"ljcoder" forKey:@"name"];

[person setValue:@"ljcoder" forKeyPath:@"name"];

不过要设置Person的自定义类的属性car的属性brand的值,就只能使用keyPath

Person *person = [[Person alloc] init];

[person setValue:@"奔驰" forKeyPath:@"car.brand"];

KVC设值流程

当我们执行setValue:forKey:后,是怎么赋值的呢?根据苹果官方文档Accessor Search Patterns,我们可以找到执行的流程。
我们以setValue:forKey:为例

[person setValue:@"ljcoder" forKey:@"name"];
  1. 查找是否存在setName方法,不存在查找_setName方法,还不存在继续查找setIsName方法,按此顺序有查到对应方法则调用对应的方法赋值。
  2. 如果第一步的方法都没有找到,检查类方法accessInstanceVariablesDirectly
    • 如果返回YES,则查找实例变量是否存在。按照_name,_isName,name,isName的顺序进行查找,如果找到就直接赋值。
    • 如果返回NO,直接进入第三步。
  3. setter方法或者实例方法没找到,则会调用setValue:forUndefinedKey:方法,默认抛出NSUndefinedKeyException的异常。

流程图如下:

KVC取值流程

同样的,我们以来valueForKey:来分析一下取值流程。

[person valueForKey:@"name"];
  1. 查找getter方法,按照getName,name,isName,_name的顺序查找,找到则执行对应方法,取值后执行第3步,没找到进入第二步。
  2. getter方法没找到,检查accessInstanceVariablesDirectly类方法
    • 返回YES,按_name,_isName,name,isName的顺序进行实例变量的查找,找到后直接取实例变量的值,执行第3步。
    • 放回NO,执行第4步。
  3. 对取到的值进行判断
    • 如果取到的值是指针类型的对象,直接返回。
    • 如果是NSNumber支持的标量类型,返回NSNumber
    • 如果是NSNumber不支持的标量类型,转换成NSValue的对象进行返回。
  4. getter方法和实例变量都没找到,则调用valueForUndefinedKey:方法,默认抛出NSUndefinedKeyException的异常。

流程图如下: