# 22.KVO底层原理

### 一、 中间类

![01](/files/-Mg_1IcV0uLE03jCCgUa)

一般提到 `KVO` 底层，大家可能都知道是生成类一个 `NSKVONotifying_XXX` 的类。但是再往深了问可能就答不出来了。

#### 1.1 中间类是动态生成的还是编译生成的？

在设置观察者之前我们通过调用 `objc_getClass("NSKVONotifying_RYModel")`，看能否获取到 `NSKVONotifying_RYModel` 类。

![2](/files/-Mg_1IcbRZWi8P46PzcV)

添加观察者之后

![3](/files/-Mg_1IccwbGyjL5Kxb8a)

\*\*\*结论：\*\*\*可见 `NSKVONotifying_RYModel` 确实是动态生成的，而不是编译完成后就已经存在的。

#### 1.2 中间类 和 本类 的关系

先猜测 `NSKVONotifying_RYModel` 是否是 `RYModel` 的子类，来验证一下。

打印自己与所有子类

```Objc
- (void)logSubClassChain:(Class)cls {
    int count = objc_getClassList(NULL, 0);
    
    NSMutableArray *tempArr = @[cls].mutableCopy;
    Class *classes = (Class*)malloc(sizeof(Class) * count);
    objc_getClassList(classes, count);
    for (int i = 0; i < count; i++) {
        if (cls == class_getSuperclass(classes[i])) {
            [tempArr addObject:classes[i]];
        }
    }
    free(classes);
    NSLog(@"Classes : %@", tempArr);
}
```

![4](/files/-Mg_1Ice3ScrbIgrU3An)

发现添加观察者后输出的多了一个 `NSKVONotifying_RYModel` 证明了它确实是 `RYModel` 的子类

\*\*\*结论：\*\*\*KVO动态创建的类是本类的一个子类。

#### 1.3 中间类的生命周期是怎样的？是否会移除？

![5](/files/-Mg_1IcfnRJ_oOYYyq7R)

\*\*\*结论：\*\*\*移除观察者，观察者释放后并不会 `NSKVONotifying_XXX` 页依旧存在。

\*\*\*思考：\*\*\*由于中间类一旦生成就一直存在，所以理论上也不宜过多使用 `KVO`。

#### 1.4 中间类的方法列表和本类有什么区别？

添加打印方法的方法：

```Objc
+ (void)logAllMethodOf:(Class)cls {
    NSLog(@"%s %@", __func__, cls);
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(cls, &count);
    for (int i = 0; i < count; i++) {
        Method method = methodList[i];
        SEL sel = method_getName(method);
        IMP imp = method_getImplementation(method);
        NSLog(@"SEL: %@ IMP: %p", NSStringFromSelector(sel), imp);
    }
    free(methodList);
    NSLog(@"\n");
}
```

![6](/files/-Mg_1Icg6b8gojX88NVT)

**RYModel**

* SEL: name IMP: 0x100003210
* SEL: .cxx\_destruct IMP: 0x1000032a0
* SEL: setName: IMP: 0x100003170
* SEL: books IMP: 0x100003240
* SEL: setBooks: IMP: 0x100003260

**NSKVONotifying\_RYModel**

* SEL: setBooks: IMP: 0x7fff212b9d2e
* SEL: setName: IMP: 0x7fff212b9d2e
* SEL: class IMP: 0x7fff2127cc06
* SEL: dealloc IMP: 0x7fff212bf9f5
* SEL: \_isKVOA IMP: 0x7fff213e3e3a

#### 1.5 中间类的方法是继承的还是重写的？

新建一个子类，重写父类的方法：

```Objc
@implementation RYSubModel

- (void)setBooks:(NSMutableArray<NSString *> *)books {
    [super setBooks:books];
}

@end
```

输出：

```
// 子类
+[KVOTool logAllMethodOf:] RYSubModel
SEL: setBooks: IMP: 0x100003240

// 本类
添加观察者之前：RYModel
+[KVOTool logAllMethodOf:] RYModel
SEL: name IMP: 0x100003160
SEL: .cxx_destruct IMP: 0x1000031f0
SEL: setName: IMP: 0x1000030c0
SEL: books IMP: 0x100003190
SEL: setBooks: IMP: 0x1000031b0

// 中间类
添加观察者之后：NSKVONotifying_RYModel
+[KVOTool logAllMethodOf:] NSKVONotifying_RYModel
SEL: setBooks: IMP: 0x7fff212b9d2e
SEL: setName: IMP: 0x7fff212b9d2e
SEL: class IMP: 0x7fff2127cc06
SEL: dealloc IMP: 0x7fff212bf9f5
SEL: _isKVOA IMP: 0x7fff213e3e3a
```

#### 1.6 KVO 开关对方法列表的影响

```Objc
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@"name"]) {
        return NO;
    }
    return YES;
}
```

停止 name 自动发送通知后，中间类的方法列表中就没有类 `setName`。

![7](/files/-Mg_1IcitsupbqowVoFs)

### 二、 ISA 指向变换

上面我们知道被观察的对象类型变成类一个中间类。那么它是怎么变的呢？

#### 2.1 改变

***添加观察者之前***

![8](/files/-Mg_1IckxfJGRmv9UbQZ)

***添加观察者之后***

![9](/files/-Mg_1IclK5-1E8i58IbC)

很明显实例的 ISA 的指向发生了变化。

#### 2.2 恢复

***开始移除观察者：***

![10](/files/-Mg_1IcnRzgi0g3SJvq-)

***移除最后一个观察者：***

![11](/files/-Mg_1IcplK5n6YEJe57V)

***观察者全部移除：***

![12](/files/-Mg_1IcqhGeNgXoXuU_X)

恢复了 `RYModel`

#### 思考：全部Key移除了观察者才ISA恢复的本类，如果不完全移除呢？

![13](/files/-Mg_1IcrceNUfNAQGQUk)

![14](/files/-Mg_1IcstrxhALrJD3Lg)

***剩了一个 Key 没有移除观察者***

![15](/files/-Mg_1Ict7eqAUFqBylZT)

到最后实例的 ISA 指向还是 中间类。

### 三、 触发通知的过程

![16](/files/-Mg_1Icug5DwhF4k0ZHO)

我们在 KVO 通知接收的地方下个断点，调用堆栈：

* tool.ryuk.name = @"Ryukie";
  * Foundation\`\_NSSetObjectValueAndNotify
    * Foundation\`-\[NSObject(NSKeyValueObservingPrivate) \_changeValueForKey:key:key:usingBlock:]
      * Foundation\`-\[NSObject(NSKeyValueObservingPrivate) \_changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]
        * Foundation\`NSKeyValueDidChange
          * Foundation\`NSKeyValueNotifyObserver
            * -\[KVOTool observeValueForKeyPath:ofObject:change:context:]

### 总结

到此，我们知道了

* 使用 KVO 过程中动态生成了中间类
* 中间类和本类是继承关系，同时重写了本类的一些方法
* 自动监听开关对与中间类的结构是有影响的
* 被观察者 ISA 的指向会发生变化，完全移除所有观察Key后会恢复。


---

# 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/22.kvo-di-ceng-yuan-li.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.
