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

# 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后会恢复。
