iOS九阴真经:十九、深入了解 KVC

749 阅读19分钟

一、KVC简介

1. 什么是KVC?

KVC(Key-Value Coding)是一种由NSKeyValueCoding非正式协议启用的机制,对象采用该机制来提供对其属性的间接访问。当对象符合键值编码时,其属性可通过字符串参数通过简洁、统一的消息传递接口进行寻址。这种间接访问机制补充了实例变量及其相关访问器方法提供的直接访问。

通常使用访问器方法来访问对象的属性。get 访问器(或 getter)返回属性的值。set 访问器(或 setter)设置属性的值。在 Objective-C 中,您还可以直接访问属性的底层实例变量。以任何这些方式访问对象属性都很简单,但需要调用特定于属性的方法或变量名称。随着属性列表的增长或变化,访问这些属性的代码也必须如此。相比之下,符合键值编码的对象提供了一个简单的消息传递接口,该接口在其所有属性中保持一致。

2.访问对象属性

2.1. 使用 key 获取属性值的方法:

  • valueForKey: - 返回由 key 参数命名的属性的值。如果根据访问器搜索模式中描述的规则无法找到由 key 命名的属性,则对象向自身发送 valueForUndefinedKey: 消息。 valueForUndefinedKey: 的默认实现会引发 NSUndefinedKeyException,但子类可能会覆盖此行为并更优雅地处理这种情况。

  • valueForKeyPath: - 返回相对于接收器的指定 keyPath 的值。keyPath序列中任何不符合特定 key 的键值编码的对象(即 valueForKey: 的默认实现无法找到访问器方法)都会接收 valueForUndefinedKey: 消息。

  • dictionaryWithValuesForKeys: - 返回相对于接收者的 key 数组的值。 该方法为数组中的每个 key 调用 valueForKey:。 返回的 NSDictionary 包含数组中所有 key 的值。

注:当您使用 keyPath 来查找属性时,如果 keyPath 中除最后一个 key 之外的任何 key 是一对多关系(即它引用一个集合,比如 NSArray),则返回的值是一个包含键的所有值的集合。``

2.2. 使用 key 设置属性值的方法:

  • setValue:forKey: - 通过 key 设置对象属性值。setValue:forKey: 的默认实现自动解包表示标量和结构的 NSNumber 和 NSValue 对象,并将它们分配给属性。如果指定的 key 对应于接收 setter 调用的对象没有的属性,则该对象向自身发送 setValue:forUndefinedKey: 消息。setValue:forUndefinedKey: 的默认实现会引发 NSUndefinedKeyException。 但是,子类可以覆盖此方法以自定义方式处理请求。

  • setValue:forKeyPath: - 在相对于接收器的指定keyPath上设置给定值。keyPath 序列中不符合特定 key 的键值编码的任何对象都会收到 setValue:forUndefinedKey: 消息。

  • setValuesForKeysWithDictionary: - 使用指定字典中的 value 设置接收器的属性,使用字典 key 来标识属性。默认实现调用setValue:forKey:为每个 key 对应的 value 赋值,根据需要用 nil 替换 NSNull 对象。

注:在默认实现中,当您尝试将非对象属性设置为 nil 值时,符合键值编码的对象会向自身发送一条 setNilValueForKey: 消息。 setNilValueForKey: 的默认实现会引发 NSInvalidArgumentException,但对象可能会覆盖此行为以替代默认值或标记值。

3. 访问集合属性

3.1. NSArray:

  • mutableArrayValueForKey: 返回一个行为类似于NSMutableArray对象的代理对象。

  • mutableArrayValueForKeyPath: 返回一个行为类似于NSMutableArray对象的代理对象。

3.2. NSSet:

  • mutableSetValueForKey: 返回一个行为类似于NSMutableSet对象的代理对象。

  • mutableSetValueForKeyPath: 返回一个行为类似于NSMutableSet对象的代理对象。

3.2 NSOrderedSet:

  • mutableOrderedSetValueForKey: 返回一个行为类似于NSMutableOrderedSet对象的代理对象。

  • mutableOrderedSetValueForKeyPath: 返回一个行为类似于NSMutableOrderedSet对象的代理对象。

当对代理对象进行操作、向其中添加对象、从中删除对象或替换其中的对象时,协议的默认实现会相应地修改底层属性。

使用场景:这些方法一般是在使用观察者的时候会用到。举个例子:

当想通过观察者观察 NSMutableArray 里元素的变化时,可以通过mutableArrayValueForKey:或者mutableArrayValueForKeyPath:方法返回的数组对象进行增删查改操作,就可以观察到NSMutableArray的变化。

4.集合运算符

4.1. 什么是集合运算符

当您发送符合键值编码的对象 valueForKeyPath: 消息时,您可以在keyPath中嵌入一个集合运算符。以符号 @ 开头的小关键字,它指定 getter 在返回数据之前应执行的以某种方式操作数据的操作。 NSObject 提供的 valueForKeyPath: 的默认实现实现了这个行为。

当keyPath包含集合运算符时,运算符之前的 keyPath 的任何部分(称为 left key path )指定相对于消息接收者要对其进行操作的集合。如果将消息直接发送到集合对象,例如 NSArray 实例,则 left key path 可能会被省略。

操作符后面的 keyPath 部分,称为 right key path,指定操作符应该处理的集合中的属性。除了@count 之外的所有集合运算符都需要一个正确的 keyPath。

4.2. 集合运算符表现出三种基本类型的行为:

  • 聚合运算符(Aggregation Operators): 以某种方式合并集合的对象,并返回一个对象,该对象通常与 right key path中命名的属性的数据类型相匹配。@count 是一个例外,它没有正确的关键路径并始终将返回一个 NSNumber 实例。

  • 数组运算符(Array Operators): 返回一个 NSArray 实例,其中包含命名集合中保存的对象的某些子集。

  • 嵌套运算符(Nesting Operators): 处理包含其他集合的集合,并根据运算符返回一个 NSArrayorNSSet 实例,该实例以某种方式组合嵌套集合的对象。

4.3. 集合运算符的行为介绍:

4.3.1. 聚合运算符:

@avg :求平均值(当指定@avg 运算符时,valueForKeyPath: 读取集合中每个元素的 right key path 指定的属性,将其转换为双精度值(用 0 替换 nil 值),并计算这些值的算术平均值。 然后它返回存储在 NSNumber 实例中的结果。)

@count :集合中对象的个数(当指定 @count 运算符时, valueForKeyPath: 返回 NSNumber 实例中集合中的对象数。 忽略正确的keyPath如果存在)。)

@max :取最大值(当指定@max 运算符时,valueForKeyPath: 在以正确 keyPath 命名的集合条目中搜索并返回最大的一个。 搜索使用 compare: 方法进行比较,该方法由许多 Foundation 类(例如 NSNumber 类)定义。 因此,由 right key path 指定的属性必须持有一个对该消息做出有意义响应的对象。 搜索忽略 nil 值的集合条目。)

@min :取最小值(当指定 @min 运算符时,valueForKeyPath: 在以正确的 keyPath 命名的集合条目中搜索并返回最小的一个。 搜索使用 compare: 方法进行比较,该方法由许多 Foundation 类(例如 NSNumber 类)定义。 因此,由 right key path 指定的属性必须持有一个对该消息做出有意义响应的对象。 搜索忽略 nil 值的集合条目。)

@sum :求和(当指定 @sum 运算符时, valueForKeyPath: 读取由集合中每个元素的 right key path 指定的属性,将其转换为双精度值(用 0 替换 nil 值),并计算这些值的总和。 然后它返回存储在 NSNumber 实例中的结果。)

4.3.2. 数组运算符:

@distinctUnionOfObjects:返回操作对象指定属性的集合-去重(当您指定@distinctUnionOfObjects运算符时,valueForKeyPath:创建并返回一个数组,该数组包含与right key path指定的属性对应的集合的不同对象。)

@unionOfObjects:返回操作对象指定属性的集合(当您指定@unionOfObjects运算符时,valueForKeyPath:创建并返回一个数组,该数组包含与right key path指定的属性对应的集合的所有对象。与 @distinctUnionOfObjects 不同,重复的对象不会被删除。)

4.3.3. 嵌套运算符:

@distinctUnionOfArrays:返回操作对象指定属性的集合-去重(当您指定@distinctUnionOfArrays运算符时,valueForKeyPath:创建并返回一个数组,该数组包含与right key path指定的属性对应的所有集合的组合的不同对象。)

@unionOfArrays:返回操作对象指定属性的集合(当您指定@unionOfArrays操作符时,valueForKeyPath:创建并返回一个数组,该数组包含与right key path指定的属性对应的所有集合的组合的所有对象,不删除重复项。)

@distinctUnionOfSets:返回操作对象指定属性的集合-去重(当您指定 @distinctUnionOfSets 操作符时,valueForKeyPath: 创建并返回一个 NSSet 对象,该对象包含与右键路径指定的属性对应的所有集合的组合的不同对象。这个运算符的行为就像 @distinctUnionOfArrays,除了它需要一个包含对象的 NSSet 实例的 NSSet 实例而不是 NSArray 实例的 NSArray 实例。 此外,它返回一个 NSSet 实例。 假设示例数据已存储在集合中而不是数组中,示例调用和结果与@distinctUnionOfArrays 显示的相同。)

5. 表示非对象值

NSObject 使用对象和非对象属性提供的键值编码协议方法的默认实现。默认实现会自动在对象参数或返回值与非对象属性之间进行转换。这允许基于键的 getter 和 setter 的签名保持一致,即使存储的属性是标量或结构。

当调用协议的 getter 时,例如 valueForKey:,默认实现会根据访问器搜索模式中描述的规则确定为指定键提供值的特定访问器方法或实例变量。 如果返回值不是一个对象,getter 使用这个值来初始化一个 NSNumber 对象(对于标量)或 NSValue 对象(对于结构)并返回它。

类似地,默认情况下,setValue:forKey: 之类的 setter 确定属性的访问器或实例变量所需的数据类型,给定一个特定的键。 如果数据类型不是对象,setter 首先向传入的值对象发送一个适当的 <type>Value 消息以提取底层数据,并将其存储起来。

:当 nil 使用非对象属性的值调用键值编码协议 setter 时,setter 没有明显的一般操作过程。因此,它向 setNilValueForKey: 接收 setter 调用的对象发送消息。此方法的默认实现引发 NSInvalidArgumentException 异常,但子类可能会覆盖此行为。

self.myClass = [[SHClass alloc] init];
ThreeFloats floats = {1., 2., 3.};
// 默认实现用getValue:消息解开该值,然后setThreeFloats:使用结果结构进行调用。
NSValue* value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[self.myClass setValue:value forKey:@"threeFloats"];
NSLog(@"%0.2f, %0.2f, %0.2f", self.myClass.threeFloats.x, self.myClass.threeFloats.y, self.myClass.threeFloats.z);
    
ThreeFloats resultFloats;
// 默认实现valueForKey:调用threeFloatsgetter,然后返回包装在NSValue对象中的结果。
NSValue* result = [self.myClass valueForKey:@"threeFloats"];
[result getValue:&resultFloats];
NSLog(@"%0.2f, %0.2f, %0.2f", resultFloats.x, resultFloats.y, resultFloats.z);

二、KVC中访问器搜索模式

NSObject 提供的 NSKeyValueCoding 协议的默认实现使用一组明确定义的规则将基于 key 的访问器调用映射到对象的底层属性。 这些协议方法使用一个关键参数来搜索它们自己的对象实例以查找访问器、实例变量和遵循某些命名约定的相关方法。

1. Getter 的基本搜索模式

valueForKey: 的默认实现:给定一个 key 参数作为输入,执行以下过程,从接收 valueForKey: 调用的类实例中操作。

1.1. 查找简单的访问器方法

在实例中按顺序查找 get<Key><key>is<Key>_<key>,如果找到,调用它并使用结果继续执行步骤 5,否则继续下一步。

1.2. 查找NSArray相关的方法

如果没有找到步骤 1 的方法,则在实例中查找这三个方法,countOf<Key>objectIn<Key>AtIndex:<key>AtIndexes:

如果找到其中的第一个和至少其他两个中的一个,则创建一个集合代理对象,该对象响应所有 NSArray 方法并返回该对象。 否则,继续执行步骤 3。

代理对象随后将它接收到的任何 NSArray 消息转换为 countOf<Key>objectIn<Key>AtIndex:<key>AtIndexes: 消息的某种组合,并将其转换为创建它的键值编码兼容对象。 如果原始对象还实现了一个可选方法,其名称类似于 get<Key>:range:,则代理对象也会在适当的时候使用它。

1.3. 查找NSSet相关的方法

如果没有找到步骤 1 或步骤 2 的方法,则查找名为 countOf<Key>enumeratorOf<Key>memberOf<Key>: 的三个方法。

如果找到这三个方法,则创建一个集合代理对象,该对象响应所有 NSSet 方法并返回该对象。 否则,继续执行步骤 4。

这个代理对象随后将它接收到的任何 NSSet 消息转换为 countOf<Key>enumeratorOf<Key>memberOf<Key> 的某种组合:消息到创建它的对象。

1.4. 间接访问成员变量

如果通过 1、2、3 步骤依次查找还是没有找到,并且如果接收者的类方法accessInstanceVariables直接返回YES,则搜索名为_<key>_is<Key><key>is<Key>的实例变量, 以该顺序。 如果找到,直接获取实例变量的值并进行步骤5,否则进行步骤6。

1.5. 返回值处理

如果检索到的属性值是一个对象指针,只需返回结果即可。

如果该值是 NSNumber 支持的标量类型,则将其存储在 NSNumber 实例中并返回。

如果结果是 NSNumber 不支持的标量类型,则转换为 NSValue 对象并返回。

1.6. 异常处理

如果以上所有的方法都查找不到,调用 valueForUndefinedKey:。 默认情况下,这会抛出异常,但可以重写valueForUndefinedKey:方法自定义处理方式。

1.7. 验证

1.7.1 普通类型

创建一个 Person 对象,实现第一点提到的到方法:

- (NSString *)getName {
    NSLog(@"%s--%@", __func__, self->_name);
    return self->_name;
}

- (NSString *)name {
    NSLog(@"%s--%@", __func__, self->_name);
    return self->_name;
}

- (NSString *)isName {
    NSLog(@"%s--%@", __func__, self->_name);
    return self->isName;
}

- (NSString *)_name {
    NSLog(@"%s--%@", __func__, self->_name);
    return self->_name;
}

调用 valueForKey: 方法:

SHPerson *p = [[SHPerson alloc] init];
[p setValue:@"jack" forKey:@"name"];
NSString *name = [p valueForKey:@"name"];
NSLog(@"完毕! -- %@", name);

image.png

验证结果就行上面第 1 点描述的一样。

1.7.2 NSArray

在 Person.h 中添加属性 arr。

@property (nonatomic, strong) NSArray *arr;

在 Person.m 中实现以下方法。

/MARK: - array
// 个数
- (NSUInteger)countOfPens{
    NSLog(@"%s",__func__);
    return [self.arr count];
}

// 获取值
- (id)objectInPensAtIndex:(NSUInteger)index {
    NSLog(@"%s - %lu",__func__, (unsigned long)index);
    return [NSString stringWithFormat:@"pen%lu", index];
}

- (NSArray *)pensAtIndexes:(NSIndexSet *)indexes {
    NSLog(@"%s",__func__);
    return self.arr;
}

调用前先把部分代码注释,根据第2点的流程一步一步的验证,验证的结果和描述的一致。

p.arr = @[@"pen0", @"pen1", @"pen2", @"pen3"];
NSArray *array = [p valueForKey:@"pens"];
NSLog(@"%@",[array objectsAtIndexes:[NSIndexSet indexSetWithIndex:1]]);
NSLog(@"%@",[array objectAtIndex:0]);
NSLog(@"%d",[array containsObject:@"pen0"]);

打印结果:

image.png

1.7.3 NSSet 类型

和 NSArray 一样,分别在 Person.h 和 Person.m 添加以下代码。

@property (nonatomic, strong) NSSet   *set;
// 个数
- (NSUInteger)countOfBooks{
    NSLog(@"%s",__func__);
    return [self.set count];
}

// 是否包含这个成员对象
- (id)memberOfBooks:(id)object {
    NSLog(@"%s",__func__);
    return [self.set containsObject:object] ? object : nil;
}

// 迭代器
- (id)enumeratorOfBooks {
    // objectEnumerator
    NSLog(@"来了 迭代编译");
    return [self.arr reverseObjectEnumerator];
}

调用前先把部分代码注释,根据第3点的流程一步一步的验证,验证的结果和描述的一致。

p.set = [NSSet setWithArray:array];
NSSet *set = [p valueForKey:@"books"];
[set enumerateObjectsUsingBlock:^(id  _Nonnull obj, BOOL * _Nonnull stop) {
    NSLog(@"set遍历 %@",obj);
}];

NSLog(@"%@",[set member:@"pen2"]);

打印结果:

image.png

1.7.4 间接访问成员变量

在 Person.m 中实现 accessInstanceVariables 并且返回 YES,根据第4点描述,分别添加_<key>_is<Key><key>is<Key>的实例变量。

@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;

调用 NSString *name = [p valueForKey:@"name"];,发现顺序和第4点描述的一致。第5第6点稍微测试一下也是正确的。

2. Setter 的基本搜索模式

setValue:forKey: 的默认实现:给定 key 和 value 参数作为输入,尝试将名为 key 的属性设置 value。

2.1. 查找简单的访问器方法

按顺序查找名为 set<Key>:_set<Key>:setIs<Key>: 的第一个访问器。 如果找到,则使用输入值(或根据需要展开的值)调用它并完成。

2.2. 间接访问成员变量

如果没有找到步骤 1 的方法,并且类方法 accessInstanceVariablesDirectly 返回 YES,则按顺序查找名称类似于 _<key>_is<Key><key>is<Key> 的实例变量。 如果找到,直接使用输入值(或解包值)设置变量并完成。

2.3. 异常处理

在未找到访问器或实例变量时,调用 setValue:forUndefinedKey:。 默认情况下,这会引发异常,但 NSObject 的子类可能会提供特定于键的行为。

2.4. 验证

在 Person 实现第 1 点的 setter 方法。

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

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

//setIs<Key>
- (void)setIsName:(NSString *)name {
    NSLog(@"%s--%@", __func__, name);
}

在验证完之后注释 setter 方法,将 accessInstanceVariablesDirectly 打开,分别添加_<key>_is<Key><key>is<Key>的实例变量。

调用代码,也是一步一步验证,验证结果和描述的是一致的。

    SHPerson *p = [[SHPerson alloc] init];
    [p setValue:@"jack" forKey:@"name"];
//    NSLog(@"%@ - %@ - %@ - %@", p->_name, p->_isName, p->name, p->isName);
//    NSLog(@"%@ - %@ - %@", p->_isName, p->name, p->isName);
//    NSLog(@"%@ - %@", p->name, p->isName);
//    NSLog(@"%@ ", p->isName);

3. 可变数组的搜素模式

mutableArrayValueForKey: 的默认实现,给定一个 key 参数作为输入,为接收访问器调用的对象内名为 key 的属性返回一个可变代理数组,使用以下过程:

3.1. 查找插入和删除的方法。

寻找一对名称类似 insertObject:in<Key>AtIndex:removeObjectFrom<Key>AtIndex:(分别对应于 NSMutableArray 原始方法 insertObject:atIndex:removeObjectAtIndex:)的方法,或名称类似 insert<Key>:atIndexes: 的方法和 remove<Key>AtIndexes:(对应于 NSMutableArray 的 insertObjects:atIndexes:removeObjectsAtIndexes: 方法)。

如果对象至少有一个插入方法和至少一个删除方法,则返回一个代理对象,该对象通过发送 insertObject:in<Key>AtIndex 的某种组合来响应 NSMutableArray 消息:, removeObjectFrom<Key>AtIndex:insert<Key>:atIndexes:remove<Key>AtIndexes: 消息发送到 mutableArrayValueForKey: 的原始接收者。

当接收到 mutableArrayValueForKey: 消息的对象也实现了一个可选的替换对象方法,其名称类似于 replaceObjectIn<Key>AtIndex:withObject:replace<Key>AtIndexes:with<Key>:,代理对象在适当的时候也会利用这些以获得最佳性能。

3.2 查找 set<Key>: 方法。

如果对象没有可变数组方法,则查找名称与模式 set<Key>: 匹配的访问器方法。 在这种情况下,通过向 mutableArrayValueForKey: 的原始接收者发出 set<Key>: 消息,返回一个响应 NSMutableArray 消息的代理对象。

3.3. 查找实例变量。

如果既没有找到可变数组方法,也没有找到访问器,并且如果接收者的类对 accessInstanceVariablesDirectly 响应 YES,则按该顺序搜索名称类似于 _<key><key> 的实例变量。

如果找到这样的实例变量,则返回一个代理对象,该对象将它接收到的每个 NSMutableArray 消息转发给实例变量的值,该值通常是 NSMutableArray 或其子类之一的实例。

3.4. 异常处理。

如果所有其他方法都失败,则返回一个可变集合代理对象,该对象在收到 NSMutableArray 消息时向 mutableArrayValueForKey: 消息的原始接收者发出 valueForUndefinedKey: 消息。

在未找到访问器或实例变量时,调用 valueForUndefinedKey:。 默认情况下,这会引发异常,但 NSObject 的子类可能会提供特定于键的行为。

3.5. 应用场景

当我们需要对可变数组元素的改变进行观察时,可以用 mutableArrayValueForKey:mutableArrayValueForKeyPath: 方法返回的可变数组对象进行增删改。

3.6. 验证

在 Person.m 中实现以下代码。

#pragma mark: - 可变数组的搜素模式
- (void)insertObject:(id)object inArrayMAtIndex:(NSUInteger)index {
    NSLog(@"%s",__func__);
}

- (void)removeObjectFromArrayMAtIndex:(NSUInteger)index {
    NSLog(@"%s",__func__);
}

- (void)insertArrayM:(NSArray *)array atIndexes:(NSIndexSet *)indexes {
    NSLog(@"%s",__func__);
}

- (void)removeArrayMAtIndexes:(NSIndexSet *)indexes {
    NSLog(@"%s",__func__);
}

- (void)replaceObjectInArrayMAtIndex:(NSUInteger)index withObject:(id)object {
    NSLog(@"%s",__func__);
}

- (void)replaceArrayMAtIndexes:(NSIndexSet *)indexes withArrayM:(NSArray *)array {
    NSLog(@"%s",__func__);
}

- (void)setArrayM:(NSMutableArray *)arrayM {
    _arrayM = arrayM;
    NSLog(@"%s",__func__);
}

并且在 Person.h 中添加 arrayM 属性和添加 _arrayM 和 arrayM 的实例变量。

{
@public
    NSMutableArray *_arrayM;
    NSMutableArray *arrayM;
}
    
@property (nonatomic, strong) NSMutableArray *arrayM;

调用代码:

SHPerson *p = [[SHPerson alloc] init];
p.arrayM = [NSMutableArray arrayWithArray:@[@"p1", @"p2", @"p3", @"p4", @"p5"]];
//    p->arrayM = [NSMutableArray arrayWithArray:@[@"p1", @"p2", @"p3", @"p4", @"p5"]];

NSMutableArray *arrayM = [p mutableArrayValueForKey:@"arrayM"];
[arrayM addObject:@"p8"];
[arrayM insertObject:@"p6" atIndex:4];
[arrayM removeObjectAtIndex:0];
[arrayM replaceObjectAtIndex:3 withObject:@"p7"];

NSLog(@"arrayM: %@", arrayM);

打印结果:

image.png

苹果的文档还有对 NSMutableOrderedSet 和 NSMutableSet 的 的搜索模式介绍,在思路上和 NSMutableArray 差不多。(NSMutableOrderedSet、NSMutableSet 的搜索模式地址)

三、自定义 KVC 流程

setValue:forKey:valueForKey:进行自定义实现。简单来说,其实就是照着 KVC 的访问器搜索模式中的流程用代码模拟实现。

这一份自定义 KVC 只是为了让自己对 KVC 的访问器搜索模式更加深刻,真正的 KVC 的底层原理不是这么实现的,如果要探索更底层的 KVC,可以去看 GitHub 中大神的自定义 KVC:DIS_KVC_KVO

1. setValue:forKey:

1.1. nil 处理

if (key == nil || key.length == 0) return;

1.2. 查找 setter 方法

//1: set<Key>: 、_set<Key>: or setIs<Key>:
// key 首字母要大写
// 拼接方法
NSString *setKey = [NSString stringWithFormat:@"set%@:", key.capitalizedString];
NSString *_setKey = [NSString stringWithFormat:@"_set%@:", key.capitalizedString];
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:", key.capitalizedString];

// 查找相关方法
if ([self respondsToSelector:NSSelectorFromString(setKey)]) {
    [self sh_performSelectorWithMethodName:setKey value:value];
    return;

}else if ([self respondsToSelector:NSSelectorFromString(_setKey)]) {
    [self sh_performSelectorWithMethodName:_setKey value:value];
    return;

}else if ([self respondsToSelector:NSSelectorFromString(setIsKey)]) {
    [self sh_performSelectorWithMethodName:setIsKey value:value];
    return;
}

1.3. 判断 accessInstanceVariablesDirectly 的返回值

if ([self.class accessInstanceVariablesDirectly] == NO) {
    @throw [NSException exceptionWithName:@"NSUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
    return;
}

1.4. 间接访问成员变量

//  2) _<key>、_is<Key>、<key>、is<Key>
// 间接访问成员变量
NSArray *ivars = [self sh_getIvarListName];
NSString *_key = [NSString stringWithFormat:@"_%@", key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@", key.capitalizedString];
NSString *isKey = [NSString stringWithFormat:@"is%@", key.capitalizedString];
if ([ivars containsObject:_key]) {
    // 获取对应的 ivar
    Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
    // 给 ivar 赋值
    object_setIvar(self , ivar, value);
    return;

}else if ([ivars containsObject:_isKey]) {
    // 获取对应的 ivar
    Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
    // 给 ivar 赋值
    object_setIvar(self , ivar, value);
    return;

}else if ([ivars containsObject:key]) {
    // 获取对应的 ivar
    Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
    // 给 ivar 赋值
    object_setIvar(self , ivar, value);
    return;

}else if ([ivars containsObject:isKey]) {
    // 获取对应的 ivar
    Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
    // 给 ivar 赋值
    object_setIvar(self , ivar, value);
    return;

}

1.5. 异常处理

//3: 如果找不到
@throw [NSException exceptionWithName:@"NSUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];

2. valueForKey:

2.1. nil 处理。

if (key == nil || key.length == 0) return nil;

2.2. 简单的访问器方法 get<Key><key>is<Key>_<key>

NSString *getKey = [NSString stringWithFormat:@"get%@", key.capitalizedString];
NSString *isKey = [NSString stringWithFormat:@"is%@", key.capitalizedString];
NSString *_key = [NSString stringWithFormat:@"_%@", key];

if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
    return [self sh_performSelectorWithMethodName:getKey];

}else if ([self respondsToSelector:NSSelectorFromString(key)]) {
    return [self sh_performSelectorWithMethodName:key];

}else if ([self respondsToSelector:NSSelectorFromString(isKey)]) {
    return [self sh_performSelectorWithMethodName:isKey];

}else if ([self respondsToSelector:NSSelectorFromString(_key)]) {
    return [self sh_performSelectorWithMethodName:_key];
}

2.3. 查找NSArray相关方法 countOf<Key>objectIn<Key>AtIndex:<key>AtIndexes:

NSString *countOfKey = [NSString stringWithFormat:@"countOf%@", key.capitalizedString];
NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:", key.capitalizedString];
NSString *keyAtIndexes = [NSString stringWithFormat:@"%@AtIndexes:", key];

if ([self respondsToSelector:NSSelectorFromString(countOfKey)]) {
    if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
        int num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
        NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
        for (int i = 0; i<num-1; i++) {
            num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
        }
        for (int j = 0; j<num; j++) {
            id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];
            [mArray addObject:objc];
        }
        return mArray;
    }
}

2.4. 查找 NSSet 相关方法 countOf<Key>enumeratorOf<Key>memberOf<Key>:

NSString *enumeratorOfKey = [NSString stringWithFormat:@"enumeratorOf%@", key.capitalizedString];
NSString *memberOfKey = [NSString stringWithFormat:@"memberOf%@:", key.capitalizedString];

if ([self respondsToSelector:NSSelectorFromString(countOfKey)]) {
    if ([self respondsToSelector:NSSelectorFromString(enumeratorOfKey)]) {
        int count = (int)[self performSelector:NSSelectorFromString(countOfKey)];
        for (int i = 0; i < count - 1; i++) {
            count = (int)[self performSelector:NSSelectorFromString(countOfKey)];
        }
        return [self sh_performSelectorWithMethodName:enumeratorOfKey];

    }else if ([self respondsToSelector:NSSelectorFromString(memberOfKey)]) {
        return [self performSelector:NSSelectorFromString(memberOfKey)];
    }
}

2.5. 判断 accessInstanceVariablesDirectly 的返回值

//  1) 判断 accessInstanceVariablesDirectly 的返回值
if ([self.class accessInstanceVariablesDirectly] == NO) {
    @throw [NSException exceptionWithName:@"NSUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}

2.6. 间接访问成员变量,_<key>_is<Key><key>is<Key>

NSArray *ivars = [self sh_getIvarListName];
NSString *_isKey = [NSString stringWithFormat:@"_is%@", key.capitalizedString];

if ([ivars containsObject:_key]) {
    // 获取对应的 ivar
    Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
    return object_getIvar(self, ivar);

}else if ([ivars containsObject:_isKey]) {
    Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
    return object_getIvar(self, ivar);

}else if ([ivars containsObject:key]) {
    Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
    return object_getIvar(self, ivar);

}else if ([ivars containsObject:isKey]) {
    Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
    return object_getIvar(self, ivar);

}

2.8. 异常处理

@throw [NSException exceptionWithName:@"NSUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];

3. 获取成员变量的方法

- (NSArray *)sh_getIvarListName{
    NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([self class], &count);
    for (int i = 0; i<count; i++) {
        Ivar ivar = ivars[i];
        const char *ivarNameChar = ivar_getName(ivar);
        NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar];
        [mArray addObject:ivarName];
    }
    free(ivars);
    return mArray.copy;
}