KVC实现原理

博客连接KVC实现原理html

KVC全称是Key Value Coding,定义在NSKeyValueCoding.h文件中。KVC提供了一种间接访问其属性方法或成员变量的机制,能够经过字符串来访问对应的属性方法或成员变量。关于KVC的实现主要依赖于其搜索规则。面试

搜索规则

在赋值过程当中,咱们会使用- (void)setValue:(id)value forKey:(NSString *)key或者(void)setValue:(id)value forKeyPath:(NSString *)keyPath;进行KVC的赋值操做。在取值过程当中,咱们会使用- (id)valueForKey:(NSString *)key;或者- (id)valueForKeyPath:(NSString *)keyPath;数组

KVC在经过key或者keyPath进行操做的时候,能够查找属性方法、成员变量等,查找的时候能够兼容多种命名。具体的查找规则在KVC官方文档中能够找到。bash

KVC的实现主要依赖于settergetter方法,因此关于命名须要符合苹果的规范。另外在搜索过程当中accessInstanceVariablesDirectly这个只读属性也起着重要的做用。这个属性表示是否容许读取实例变量的值,若是为YES则在KVC查找的过程当中,从内存中读取属性实例变量的值,默认为YES。app

赋值原理

setValue:forKey:为例,其内部实现主要有如下步骤:测试

  1. set<Key>:_set<Key>的顺序查找对应命名的setter方法,若是找到的话,调用这个方法并将值传进去(根据须要进行对象转换);ui

  2. 若是没有发现setter方法,可是accessInstanceVariablesDirectly类属性返回YES,则按_<key>_is<Key><key>is<Key>的顺序查找一个对应的实例变量。若是发现则将value赋值给实例变量;spa

  3. 若是没有发现setter方法或实例变量,则调用setValue:forUndefinedKey:方法,默认抛出一个异常,可是一个NSObject的子类能够提出合适的行为。指针

接着咱们用代码进行相关的测试:code

实验1:验证setter方法

// model1
@interface KVCTestModel1 : NSObject

@end

@implementation KVCTestModel1

- (void)setName:(NSString *)name {
    NSLog(@"%s", __func__);
}

- (void)_setName:(NSString *)name {
    NSLog(@"%s", __func__);
}

@end

// model2
@interface KVCTestModel2 : NSObject

@end

@implementation KVCTestModel2

- (void)_setName:(NSString *)name {
    NSLog(@"%s", __func__);
}

@end

// model3
@interface KVCTestModel3 : NSObject

@end

@implementation KVCTestModel3

@end

// 调用
- (void)_testKVC {
    KVCTestModel1 *model1 = [[KVCTestModel1 alloc] init];
    [model1 setValue:@"Nero" forKey:@"name"];
    
    KVCTestModel2 *model2 = [[KVCTestModel2 alloc] init];
    [model2 setValue:@"Nero" forKey:@"name"];
    
    KVCTestModel3 *model3 = [[KVCTestModel3 alloc] init];
    [model3 setValue:@"Nero" forKey:@"name"];
}
复制代码

执行结果以下:

kvc_setter1

实验2:验证明例变量

// model1
@interface KVCTestModel1 : NSObject {
    NSString *_name;
    NSString *_isName;
    NSString *name;
    NSString *isName;
}

@end

// model2
@interface KVCTestModel2 : NSObject {
    NSString *_isName;
    NSString *name;
    NSString *isName;
}

@end

// model3
@interface KVCTestModel3 : NSObject {
    NSString *name;
    NSString *isName;
}

@end

// model4
@interface KVCTestModel4 : NSObject {
    NSString *isName;
}

@end

// 调用
- (void)_testKVC {
    self.kvcTestModel1 = [[KVCTestModel1 alloc] init];
    [self.kvcTestModel1 setValue:@"Nero" forKey:@"name"];
    
    self.kvcTestModel2 = [[KVCTestModel2 alloc] init];
    [self.kvcTestModel2 setValue:@"Nero" forKey:@"name"];

    self.kvcTestModel3 = [[KVCTestModel3 alloc] init];
    [self.kvcTestModel3 setValue:@"Nero" forKey:@"name"];

    self.kvcTestModel4 = [[KVCTestModel4 alloc] init];
    [self.kvcTestModel4 setValue:@"Nero" forKey:@"name"];
}
复制代码

执行结果以下:

kvc_setter2

另外若是设置accessInstanceVariablesDirectly返回为NO,即便有符合命名规范的实例变量名,KVC也没法赋值成功;setValue:forUndefinedKey:默认会抛出一个异常,你能够用重写这个方法用来拦截。

赋值原理流程图以下:

kvc_setter_process.jpg

取值原理

valueForKey:为例,其内部实现主要有如下几步:

  1. 经过getter方法搜索实例,以get<Key>, <key>, is<Key>, _<key>的顺序搜索符合规则的方法,若是有,就调用对应的方法;

  2. 若是没有发现简单getter方法,而且在类方法accessInstanceVariablesDirectly是返回YES的的状况下搜索一个名为_<key>_is<Key><key>is<Key>的实例;

  3. 若是返回值是一个对象指针,则直接返回这个结果;若是返回值是一个基础数据类型,可是这个基础数据类型是被NSNumber支持的,则存储为NSNumber并返回;若是返回值是一个不支持NSNumber的基础数据类型,则经过NSValue进行存储并返回;

  4. 在上述状况都失败的状况下调用valueForUndefinedKey:方法,默认抛出异常,可是子类能够重写此方法。

因为和前面的赋值原理实验类似,这里就不添加相关的验证代码了。另外valueForKey:返回的结果还多是数组或者其余集合类型,因此在上面第1步和第2步之间还有一些其余的规则,这些规则用来判断是不是数组或者其余集合类型的规则,可是我以为忽略这些规则跟总体流程理解冲突不大,因此就忽略掉了(具体的在官在KVC官方文档中能够找到。)。

补充:

面试题分析: KVC可否可以触发KVO

答案是确定的。

测试代码以下:

@interface KVCTestModel1 : NSObject {
    @public
    NSString *_name;
}

@end

@implementation KVCTestModel1

@end

// 测试代码
KVCTestModel1 *model1 = [[KVCTestModel1 alloc] init];
[model1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];

// 直接修改为员变量
model1 -> _name = @"Nero1";
NSLog(@"%@", [model1 valueForKey:@"name"]);

// 手动触发KVO
[model1 willChangeValueForKey:@"name"];
model1 -> _name = @"Nero2";
[model1 didChangeValueForKey:@"name"];
NSLog(@"%@", [model1 valueForKey:@"name"]);

// KVC赋值
[model1 setValue:@"Nero3" forKeyPath:@"name"];
NSLog(@"%@", [model1 valueForKey:@"name"]);

[model1 removeObserver:self forKeyPath:@"name"];
复制代码

打印结果:

经过上面的代码咱们能够认为,在以KVC的方式对变量进行赋值的时候,会判断该对象是否使用了KVO,若是是,则会触发KVO。

相关文章
相关标签/搜索