# 20.详解KVC

## 前言

## 一、 KVC set/get 过程

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

![1](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F8a3ad4d7b9cd7c075b0ddd7909b2355aeb6b3a1b.png?generation=1628396948349997\&alt=media)

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

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

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

#### 1.1.1

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

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

如果找到，则调用它并使用结果继续执行[步骤 5](#115)，否则继续下一步

***验证一下***

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

![getName](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F54012e1b01fdc9bf0a7b6791f816ea5abbe35b6e.png?generation=1628396948265085\&alt=media)

![name](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F02902893fa232c0952d7aeebe52a62a5203701e1.png?generation=1628396947873231\&alt=media)

![isName](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fce1ab6dc501bf26c72d5ed4d8ce89b0f7534a5e0.png?generation=1628396947536422\&alt=media)

![\_name](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F5cbc0a81f64ff17f2ad6cdbf78cf526d16980667.png?generation=1628396948287689\&alt=media)

#### 1.1.2

在实例中搜索名称

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

> 对应于 `NSArray`

如果找到这些中的第一个和其他两个中的至少一个，则创建一个响应所有NSArray方法的集合代理对象并返回该对象。否则，继续执行 [步骤 3](#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](#114)

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

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

#### 1.1.4

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

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

的成员变量，如果找到，直接获取实例变量的值并进行[步骤5](#115)，否则进行[步骤6](#116)。

***验证***

![\_name](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F33b23d51c4f2c789e490f45895c5140815505240.png?generation=1628396947257580\&alt=media)

![\_isName\_](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fe5ba03261316ad642b590a6716ea011b56970533.png?generation=1628396947868416\&alt=media)

![name](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F1e834d42f3eb21cdbb8291cf39db97ddcb35b129.png?generation=1628396948406648\&alt=media)

![isName](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F04cbcd3c51aaa0bfe3ec6ecd8ea4695ae2e8cb6e.png?generation=1628396948428233\&alt=media)

#### 1.1.5

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

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

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

#### 1.1.6

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

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

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

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

如果找到，调用。

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

![setName](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fa875e822c06513ad93eee882bc2be5f602d9b68d.png?generation=1628396948084772\&alt=media)

![\_setName](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F0b671558133255ab7da6153948cd8ca17c7e1062.png?generation=1628396948347812\&alt=media)

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

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

如果找到，设置。

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

![\_name](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F2c7699fb79bcd5f9c4bd5551379c59036ff205e9.png?generation=1628396947885038\&alt=media)

![\_isName\_](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fb9ff2d0a61bd6ce537c5a1ea459865feef37e735.png?generation=1628396949270050\&alt=media)

![name](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F3ba75c28178c2257536103448604963d4294e012.png?generation=1628396947360122\&alt=media)

![\_isName\_](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F6e800c3b81bba281b43ec6290b38d065674b8299.png?generation=1628396949081945\&alt=media)

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

## 二、 关于 accessInstanceVariablesDirectly 的思考

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

如果 NO，会怎样呢？

![accessInstanceVariablesDirectly](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F2ed57a043ebf9f5fc42823a9f75a7301482a6e89.png?generation=1628396947586692\&alt=media)

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

## 参考

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