前言

我们知道, OC 中有三种对象:实例对象,类对象,元类对象。他们的关系如图:

OC中三种对象

我们复习一下这张图:

isa 属性用来沟通这三种对象

实例对象的 isa 指向(当前类的)类对象,类对象的 isa 指向(当前类的)元类对象。

superclass 属性用来找到这三种对象的父类

比如 Student 继承自 Person,Person 继承自 NSObject,那么在查找Student 内的属性,或者调用 Student 的方法时,就要用到 superclass:
如果 Student 没有 eat 方法,那么就通过 superclass 找到 Person,看看有没有 eat 方法,有的话直接调用。

复习

我们先复习一下 KVO 和 KVC。
KVO 全称 key value observing,翻译成“键值观察”,是 Foundation 框架中的重要组成部分,借助了 Runtime 的特性,通过 C 语言实现。
主要是如下两个方法:

@interface NSObject(NSKeyValueObserverRegistration)
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));

- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end

假设我们有 KSPerson 类:

@interface KSPerson : NSObject
@property (assign, nonatomic) int age;
@property (assign, nonatomic) int height;
@end

以及测试代码:

#import "ViewController.h"
#import "KSPerson.h"
#import <objc/runtime.h>

@interface ViewController ()
@property (strong, nonatomic) KSPerson *person1;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.person1 = [[KSPerson alloc] init];
    self.person1.age = 1;
    self.person1.height = 11;
    
    NSLog(@"绑定前:%@",object_getClass(self.person1));
    
    // 给person1对象添加KVO监听
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
    [self.person1 addObserver:self forKeyPath:@"height" options:options context:@"456"];
    
    Class kvoClass = object_getClass(self.person1);
    Class sprClass = class_getSuperclass(kvoClass);
    NSLog(@"绑定后的类变成了:%@,父类:%@",kvoClass,sprClass);

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    self.person1.age = 20;
    self.person1.height = 30;
}

- (void)dealloc {
    [self.person1 removeObserver:self forKeyPath:@"age"];
    [self.person1 removeObserver:self forKeyPath:@"height"];
}

// 当监听对象的属性值发生改变时,就会调用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}

@end

那么点击屏幕的时候应该会触发,并且打印信息:

绑定前:KSPerson
绑定后的类变成了:NSKVONotifying_KSPerson,父类:KSPerson

这证实了:KVO 动态生成了新的类 NSKVONotifying_KSPerson。 接下来,我们深入剖析一下。

动态生成子类

先告诉大家结果,KVO 内部实现是这样的:

// 1. 重写 setter 方法,在设置值前后触发通知
- (void)setAge:(int)age {
    [self willChangeValueForKey:@"age"];
    [super setAge:age]; // 调用原始实现
    [self didChangeValueForKey:@"age"];
}

// 2. 重写 class 方法,隐藏动态子类的存在
- (Class)class {
    return class_getSuperclass(object_getClass(self)); // 返回父类(KSPerson)
}

// 3. 重写 dealloc 方法,清理资源

我们最关注的部分无疑是动态生成子类是如何做到的。仍然直接给出答案:
改变对象的 isa指针:

// 添加观察者时
object_setClass(originalObject, generatedSubclass);

// 移除观察者时  
object_setClass(observedObject, originalClass);

参考

Introduction to Key-Value Observing Programming Guide

apple-oss-distributions 之 Runtime 源码