# 21.KVO几个被忽视的细节

## 前言

> Important: In order to understand key-value observing, you must first understand key-value coding.

上面是 KVO 官方文档中的一句重要提醒：***重要：为了能理解 KVO ，必须先理解 KVC。***

## 一、 Options

它影响通知中提供的更改字典的内容以及生成通知的方式。

### NSKeyValueObservingOptionOld

更改之前的值

### NSKeyValueObservingOptionNew

接收新值

您可以通过这些选项的按位 OR 接收旧值和新值。

### NSKeyValueObservingOptionInitial

您指示被观察对象使用选项 `NSKeyValueObservingOptionInitial` 发送立即更改通知（在 `addObserver:forKeyPath:options:context:returns` 之前）。您可以使用这个额外的一次性通知来获取初始值。

### NSKeyValueObservingOptionPrior

您可以通过包含选项 `NSKeyValueObservingOptionPrior` 指示被观察对象在属性更改之前发送通知（除了更改之后的通常通知之外）。

变更字典通过包含键 `NSKeyValueChangeNotificationIsPriorKey` 和包含 `YES` 的 `NSNumber` 值来表示预更改通知。

## 二、 Context

```
[tool.ryuk addObserver:tool forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
```

在平时使用时，我们一般就传个 `NULL` 但是他到底是做什么的有想过么？

`addObserver:forKeyPath:options:context:` 消息中的 `Context` 指针可以包含任意数据，这些数据将在相应的更改通知中传递回观察者。您可以指定 `NULL` 并完全依赖键路径字符串来确定更改通知的来源，但是这种方法可能会导致对象的父类由于不同原因也在观察相同的键路径时出现问题。

一种更安全、更可扩展的方法是使用 `Context` 来确保您收到的通知是发送给您的观察者而不是父类的。

类中唯一命名的静态变量的地址是一个很好的 `Context` 。在父类或子类中以类似方式选择的 `Context` 不太可能重叠。您可以为整个类选择一个 `Context` ，并依靠通知消息中的关键路径字符串来确定发生了什么变化。

或者，您可以为每个观察到的键路径创建一个不同的 `Context` ，这完全绕过了字符串比较，从而提高了通知解析的效率。

***创建\*\*\*\* ****`Context`**** \*\*\*\*指针***

```
static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
```

***注册观察者***

```
- (void)registerAsObserverForAccount:(Account*)account {
    [account addObserver:self
              forKeyPath:@"balance"
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                 context:PersonAccountBalanceContext];

    [account addObserver:self
              forKeyPath:@"interestRate"
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                  context:PersonAccountInterestRateContext];
}
```

## 三、 对可变数组的观察

![1](/files/-MgZK_NhVtyGqM3mjjIN)

相对可变数组进行 `KVO` 很可能会写出这样的代码。但是明显是没用的。这就不得不提文章开始的那句官方提示 ***重要：为了能理解 KVO ，必须先理解 KVC。***

[访问集合属性 Accessing Collection Properties](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/AccessingCollectionProperties.html#//apple_ref/doc/uid/10000107i-CH4-SW1)

为了使可变集合内部的变化，能够被监听到需要调整一下 API

* `mutableArrayValueForKey:` and `mutableArrayValueForKeyPath:`
  * return `NSMutableArray`
* `mutableSetValueForKey:` and `mutableSetValueForKeyPath:`
  * return `NSMutableSet`
* `mutableOrderedSetValueForKey:` and `mutableOrderedSetValueForKeyPath:`
  * return `NSMutableOrderedSet`

![02](/files/-MgZK_NjcJGAXaPzp38d)

## 四、 KVO 开关控制

> 如果你写了一个库，不希望内部的一些属性被监听到。有没有办法阻止掉呢？

```
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;
```

系统提供了上面的方法，默认是返回 `YES`，即：所有属性都可以被监听。如果你不希望部分或者全部被监听可以返回 `NO`。

### 4.1 全部禁止

![03](/files/-MgZK_NlURve2w7LWVL0)

没有触发变动的通知

### 4.2 部分禁止

![04](/files/-MgZK_Nm3nmtfTAoikez)

`name` 的通知没有触发，`books` 的通知正常触发了。

### 4.3 禁止后手动发送通知

![05](/files/-MgZK_NnychP642GQ0Fu)

虽然 `automaticallyNotifiesObserversForKey` 禁止了通知，但是手动触发后依旧可以正常监听到

## 参考

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


---

# 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/21.kvo-ji-ge-bei-hu-shi-de-xi-jie.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.
