> For the complete documentation index, see [llms.txt](https://ryukiedev.gitbook.io/wiki/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ryukiedev.gitbook.io/wiki/ios/di-ceng/20.-xiang-jie-kvc.md).

# 20.详解KVC

## 前言

## 一、 KVC set/get 过程

`NSObject` 的分类提供了 `NSKeyValueCoding协议` 的默认实现。

![1](/files/-MgZ0N-jewkBgG9V3uoO)

> 本文中的描述使用`<key>`或`<key>`作为键字符串的占位符，该字符串在键值编码协议方法之一中作为参数出现，然后该方法将其用作辅助方法调用或变量名称查找的一部分。映射的属性名称遵循占位符的大小写。例如，对于 getter `<key>` 和 `is<key>`，名为 `hidden` 的属性映射到 `hidden` 和 `isHidden`。

### 1.1 基本 Getter 的搜索过程

`valueForKey:` 的默认实现，给定一个key参数作为输入，执行以下过程。

#### 1.1.1

在实例中按顺序搜索`方法`：

* get`<key>`
* `<key>`
* is`<key>`
* \_`<key>`

如果找到，则调用它并使用结果继续执行[步骤 5](/wiki/ios/di-ceng/20.-xiang-jie-kvc.md#115)，否则继续下一步

***验证一下***

```
@interface RYModel : NSObject {
    @public
    NSString *_name;
    NSString *name;
    NSString *isName;
    NSString *_isName;
}
@end
```

![getName](/files/-MgZ0N-oOg12O8QpQTAz)

![name](/files/-MgZ0N-p659bZaYKgfJu)

![isName](/files/-MgZ0N-qMA9-Ysy9x6fO)

![\_name](/files/-MgZ0N-rUi9SPCaxZCxC)

#### 1.1.2

在实例中搜索名称

* countOf`<key>` 和&#x20;
* objectIn`<key>`AtIndex:
* `<key>`AtIndexes:

> 对应于 `NSArray`

如果找到这些中的第一个和其他两个中的至少一个，则创建一个响应所有NSArray方法的集合代理对象并返回该对象。否则，继续执行 [步骤 3](/wiki/ios/di-ceng/20.-xiang-jie-kvc.md#113)

代理对象随后将任何 NSArray 接收到的一些组合的消息 ***countOf`<key>`*** ， ***objectIn`<key>`AtIndex:*** 和 ***`<key>`AtIndexes:*** 消息给键-值编码创建它兼容的对象。

如果原始对象还实现了一个可选的方法，其名称类似于 ***get`<key>`:range:*** ，则代理对象也会在适当的时候使用它。

> 实际上，与 `KVC` 兼容的对象一起工作的代理对象允许底层属性表现得好像它是 `NSArray` ，即使它不是。

#### 1.1.3

如果没有找到简单的访问方法或阵列访问方法组，寻找：

* countOf`<key>`
* enumeratorOf`<key>`
* memberOf`<key>`

> 对应于 `NSSet` 类

如果找到所有三个方法，则创建一个响应所有 `NSSet` 方法的集合代理对象并返回该对象。否则，继续执行[步骤 4](/wiki/ios/di-ceng/20.-xiang-jie-kvc.md#114)

此代理对象随后将任何 `NSSet` 接收到的一些组合信息 ***countOf`<key>`*** ， ***enumeratorOf`<key>`*** 和 ***memberOf`<key>`:*** 消息以创建它的对象。

> 实际上，与 `KVC` 兼容的对象一起工作的代理对象允许底层属性表现得好像它是 `NSSet`，即使它不是。

#### 1.1.4

如果前面都没有找到，且类方法 `accessInstanceVariablesDirectly` 返回 `YES（默认）` ，按顺序查找名为

* \_`<key>`
* \_is`<key>`
* `<key>`
* is`<key>`

的成员变量，如果找到，直接获取实例变量的值并进行[步骤5](/wiki/ios/di-ceng/20.-xiang-jie-kvc.md#115)，否则进行[步骤6](/wiki/ios/di-ceng/20.-xiang-jie-kvc.md#116)。

***验证***

![\_name](/files/-MgZ0N-vBqt1BMrneJj0)

![\_isName\_](/files/-MgZ0N-wtjBwsvghLGkg)

![name](/files/-MgZ0N-yjJColsV9FCbl)

![isName](/files/-MgZ0N-zOS55iOfaakRO)

#### 1.1.5

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

如果该值是 `NSNumber` 支持的基础数据类型，则将其存储在一个 `NSNumber` 实例中并返回该实例。

如果结果是 `NSNumber` 不支持的数据类型，则转换为 `NSValue` 对象并返回。

#### 1.1.6

如果所有其他方法都失败，请调用 `valueForUndefinedKey:`。 默认情况下，这会引发异常，但NSObject的子类可能会提供特定于Key的处理。

### 1.2 基本 Setter 的搜索过程

`setValue:forKey:` 的流程按顺序查找`方法`：

* set`<key>`:
* \_set`<key>`

如果找到，调用。

***验证方法查找***

![setName](/files/-MgZ0N01WicpgBP7lvvz)

![\_setName](/files/-MgZ0N02hDB0vQHWxkMN)

如果没有找到，如果类方法 `accessInstanceVariablesDirectly` 返回 `YES（默认）` ，按顺序寻找一个实例变量与名称类似：

* \_`<key>`
* \_is`<key>`
* `<key>`
* is`<key>`

如果找到，设置。

***验证变量查找***

![\_name](/files/-MgZ0N03pvfFtV9kkzda)

![\_isName\_](/files/-MgZ0N042eXKZdeK_cDH)

![name](/files/-MgZ0N05W4lGNiGuzuwM)

![\_isName\_](/files/-MgZ0N06gW2Y6q2GxO9W)

如果找不到，调用 `setValue:forUndefinedKey:` 。 默认情况下，这会引发异常，但 `NSObject` 的子类可能会提供特定于Key的处理。

## 二、 关于 accessInstanceVariablesDirectly 的思考

控制是否可以通过 KVC 给成员变量赋值，默认 `YES`。

如果 NO，会怎样呢？

![accessInstanceVariablesDirectly](/files/-MgZ0N07-9zUnCvgBPfq)

这里给了我一点启发，如果自己封装的一些东西不希望使用者通过 `KVC` 修改一些私有成员变量的话可以在这里返回 `NO`。

## 参考

[Key-Value Coding Programming Guide](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/index.html#//apple_ref/doc/uid/10000107i)
