# 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)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ryukiedev.gitbook.io/wiki/ios/di-ceng/20.-xiang-jie-kvc.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
