[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fMgXghLzgcxHjA6k4P0JINH7b6bFMmPE1MTeHp3_TqHg":3},{"code":4,"message":5,"data":6},200,"成功",{"id":7,"createdAt":8,"title":9,"content":10,"summary":11,"image":12,"uid":13,"user":14,"categoryId":21,"category":22,"subCategoryId":24,"subCategory":25,"comments":27,"status":17,"reason":12,"notice":12,"visitCount":28,"commentCount":29,"keywords":30},58,"2024-01-31T04:03:11.44Z","iOS的KVO底层实现","# 概述：\n\nKVO属于观察者模式，在很多地方都会用到，比如响应式编程。\n\n1.  KVO只是观察属性的set方法，属性是对成员变量及其set、get方法的封装。\n```\n- (void)touchesBegan:(NSSet\u003CUITouch *> *)touches withEvent:(UIEvent *)event {\n    static int a = 0;\n    \n//    _p.dog.age = a++;\n//    _p.dog.level = a++;\n\n    // 成员变量这样子不能被监听到\n    _p->_name = [NSString stringWithFormat:@\"%d\", a++]; \n}\n```\n\n2. 添加观察\n```\n/*\nNSKeyValueObservingOptionNew        返回新值\nNSKeyValueObservingOptionOld        返回旧值\nNSKeyValueObservingOptionInitial    注册的时候就会发一次通知，改变的时候也会发通知\nNSKeyValueObservingOptionPrior      改变之前发一次，改变之后发一次\n*/\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    _p = [[Person alloc] init];\n    // 添加观察者\n    [_p addObserver:self forKeyPath:@\"name\" options:NSKeyValueObservingOptionNew context:nil];\n}\n- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary\u003CNSKeyValueChangeKey,id> *)change context:(void *)context {\n    NSLog(@\"%@\", change);\n}\n```\n观察者方法中有一个`change`，这个里面有一个`kind`的key，有以下几种：\n```\ntypedef NS_ENUM(NSUInteger, NSKeyValueChange) {\n    NSKeyValueChangeSetting = 1,\n    NSKeyValueChangeInsertion = 2,\n    NSKeyValueChangeRemoval -= 3,\n    NSKeyValueChangeReplacement = 4,\n};\n```\n\n# 概述：\n\nKVO属于观察者模式，在很多地方都会用到，比如响应式编程。\n\n## 1.  KVO只是观察属性的set方法，属性是对成员变量及其set、get方法的封装。\n\n    - (void)touchesBegan:(NSSet\u003CUITouch *> *)touches withEvent:(UIEvent *)event {\n        static int a = 0;\n        \n    //    _p.dog.age = a++;\n    //    _p.dog.level = a++;\n\n        // 成员变量这样子不能被监听到\n        _p->_name = [NSString stringWithFormat:@\"%d\", a++]; \n    }\n\n## 2. 添加观察者：\n\n    /*\n    NSKeyValueObservingOptionNew        返回新值\n    NSKeyValueObservingOptionOld        返回旧值\n    NSKeyValueObservingOptionInitial    注册的时候就会发一次通知，改变的时候也会发通知\n    NSKeyValueObservingOptionPrior      改变之前发一次，改变之后发一次\n    */\n    - (void)viewDidLoad {\n        [super viewDidLoad];\n        \n        _p = [[Person alloc] init];\n        // 添加观察者\n        [_p addObserver:self forKeyPath:@\"name\" options:NSKeyValueObservingOptionNew context:nil];\n    }\n    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary\u003CNSKeyValueChangeKey,id> *)change context:(void *)context {\n        NSLog(@\"%@\", change);\n    }\n\n观察者方法中有一个`change`，这个里面有一个`kind`的key，有以下几种：\n\n    typedef NS_ENUM(NSUInteger, NSKeyValueChange) {\n        NSKeyValueChangeSetting = 1,\n        NSKeyValueChangeInsertion = 2,\n        NSKeyValueChangeRemoval -= 3,\n        NSKeyValueChangeReplacement = 4,\n    };\n\n## 3. KVO有两种方式触发\n*   自动\\\n    通过`@interface NSObject(NSKeyValueObservingCustomization)`中的\n```\n/* Return YES if the key-value observing machinery should automatically invoke -willChangeValueForKey:/-didChangeValueForKey:, -willChange:valuesAtIndexes:forKey:/-didChange:valuesAtIndexes:forKey:, or -willChangeValueForKey:withSetMutation:usingObjects:/-didChangeValueForKey:withSetMutation:usingObjects: whenever instances of the class receive key-value coding messages for the key, or mutating key-value coding-compliant methods for the key are invoked. Return NO otherwise. Starting in Mac OS 10.5, the default implementation of this method searches the receiving class for a method whose name matches the pattern +automaticallyNotifiesObserversOf\u003CKey>, and returns the result of invoking that method if it is found. So, any such method must return BOOL too. If no such method is found YES is returned.\n*/\n+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;\n```\n默认`YES`来触发。\n\n*   手动\\\n    即通过如下设置，手动触发\n```\n// KVO两种模式：1. 自动， 2. 手动\n// 默认是自动模式\n+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {\n    return NO;\n}\n```\n```\n- (void)touchesBegan:(NSSet\u003CUITouch *> *)touches withEvent:(UIEvent *)event {\n    static int a = 0;\n    // 手动触发KVO\n    [_p willChangeValueForKey:@\"name\"];\n    _p.name = [NSString stringWithFormat:@\"%d\", a++];\n    [_p didChangeValueForKey:@\"name\"];\n}\n```\n## 4. 属性的依赖（依然可以observe）\n```\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    _p = [[Person alloc] init];\n    // 添加观察者\n    [_p addObserver:self forKeyPath:@\"dog.age\" options:NSKeyValueObservingOptionNew context:nil];\n}\n\n- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary\u003CNSKeyValueChangeKey,id> *)change context:(void *)context {\n    NSLog(@\"%@\", change);\n}\n\n- (void)touchesBegan:(NSSet\u003CUITouch *> *)touches withEvent:(UIEvent *)event {\n    static int a = 0;\n    _p.dog.age = a++;\n}\n```\n## 5. `KVO`监听的是`set`方法，对于像是容器来的比如`NSMutableArray`，则需要结合`KVC`。\n监听容器类属性的方法有：\n### 5.1. 例如新建`NSMutableArray`，然后通过`set`方法赋值给属性;（low的做法）\n### 5.2. Apple提供了一个途径：\n```\n// 通过KVO观察容器属性的变化，利用KVC方法\n    NSMutableArray *tempArr = [_p mutableArrayValueForKey:@\"arr\"];\n    [tempArr addObject:@\"objc\"];\n//    [_p.arr addObject:@\"1\"];\n```\n这样就可以通过`addObserver`方法观察得到了，苹果的这种做法，通过断点，可以得知，`tempArr`其实并不是`NSMutableArray`类型，而是`NSKeyValueNotifyingMutableArray`，是`NSMutableArray`的子类，通过重写`addObject`方法来手动`willChange`和`didChange`。\n\n### 5.3. 监听`NSMutableArray`的`count`\n*   通过KVO `addObserver`crash, 观察`\"@count\"`也会crash\n*   通过KVC `id count = [arr valueForKey:@\"count\"]`会crash\n*   通过集合运算符 `[arr valueForKey:@\"@count\"]`可以成功获取到count\n\n## 6. 如果要观察Person类中的Dog属性的age和level属性，\n```\n    // 添加观察者\n    //    [_p addObserver:self forKeyPath:@\"dog.age\" options:NSKeyValueObservingOptionNew context:nil];\n    //    [_p addObserver:self forKeyPath:@\"dog.level\" options:NSKeyValueObservingOptionNew context:nil];\n        // 如果观察dog的属性的话，可以直接观察dog，然后在person类中做修改\n        [_p addObserver:self forKeyPath:@\"dog\" options:NSKeyValueObservingOptionNew context:nil];\n        \n    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary\u003CNSKeyValueChangeKey,id> *)change context:(void *)context {\n        NSLog(@\"%@\", change);\n    }\n\n    - (void)touchesBegan:(NSSet\u003CUITouch *> *)touches withEvent:(UIEvent *)event {\n        static int a = 0;\n        \n        _p.dog.age = a++;\n        _p.dog.level = a++;\n    }\n```\n在`Person.m`文件里：\n```\n// 返回一个容器，里面放字符串类型\n+ (NSSet\u003CNSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {\n//    NSLog(@\"%@\", key);\n    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];\n    if ([key isEqualToString:@\"dog\"]) {\n        NSArray *arr = @[@\"_dog.level\", @\"_dog.age\"];\n        keyPaths = [keyPaths setByAddingObjectsFromArray:arr];\n    }\n        \n    return keyPaths;\n}\n```\n## 7. 观察的实现原理\n通过\n```\n// 添加观察者\n    [_p addObserver:self forKeyPath:@\"name\" options:NSKeyValueObservingOptionNew context:nil];\n```\n会创建一个继承自`Person`的子类`NSKVONotifying_Person`，记住以下：\n*   方法的调用和类型有关\n*   成员变量和对象有关系\n\n`_p`的类型从Person变成了子类`NSKVONotifying_Person`，即`_p`的`isa`指针指向了`NSKVONotifying_Person`\n\n## 8. 消息发送\n消息发送时，首先找类，然后找这个类`Class`中的方法`SEL`编号，对应着方法的实现，然后就能找到代码区。如果找不到，那么就会根据`isa`指针，找到父类，找父类的方法。\n\n## 9. 子类继承父类\n一个类继承父类的情况下，子类不会有父类的方法，如果子类调用的方法在子类中没有，那么就会通过`isa`指针去找父类的方法。子类重写父类的方法，实质上就是添加方法。\n\n## 10 使用`runtime`重新实现系统的`KVO`\n### 10.1 新建分类：`NSObject+CSKV`\n```\n@interface NSObject (CSKVO)\n\n- (void)CS_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;\n\n@end\n\n#import \"NSObject+CSKVO.h\"\n#import \u003Cobjc/runtime.h>\n\n@implementation NSObject (CSKVO)\n\n- (void)CS_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {\n    // 1. 自定义一个NSKVONotifying_Person子类\n    NSString *oldClassName = NSStringFromClass(self.class);\n    NSString *newClassName = [@\"CS_\" stringByAppendingString:oldClassName];\n    // 创建一个类\n    Class myClass = objc_allocateClassPair(self.class, newClassName.UTF8String, 0);\n    // 注册该类\n    objc_registerClassPair(myClass);\n    // 2. 动态修改\n    object_setClass(self, myClass);\n    // 3. 添加setName方法！\n    class_addMethod(myClass, @selector(setName:), (IMP)haha, \"v@:@\"); // “v@:@”：v表示返回值void, @表示第一个参数是OC类型，:表示方法编号SEL，@表示最后一个参数是oc类型\n}\n\n// OC方法的调用里面有两个隐藏参数：id self, SEL _cmd\nvoid haha(id self, SEL _cmd, NSString *newName) { // 这个地方如果只给参数：NSString *newName, 那么newName的值就知识CS_Person对象\n    NSLog(@\"子类setname %@\", newName);\n    // 修改name属性\n    // 通知外界willChange, didChange.\n}\n\n@end\n```\n\n### 10.2 添加观察者\n```\n[_p CS_addObserver:self forKeyPath:@\"name\" options:NSKeyValueObservingOptionNew context:nil];\n\n- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary\u003CNSKeyValueChangeKey,id> *)change context:(void *)context {\n    NSLog(@\"%@\", change);\n}\n```\n\n### 10.3 修改属性\n```\n//    _p.name = [NSString stringWithFormat:@\"%d\", a++];\n    // 上面等价于下面的消息机制\n    objc_msgSend(_p, @selector(setName:), [NSString stringWithFormat:@\"%d\", a++]);\n```\n\n## 11 数组`NSMutableArray`\n\n`NSMutableArray *arr = [NSMutableArray arrayWithCapacity:1];`\n\n*   关于容量，数组在内存中，如果内存不够用了，容量会成倍(\\*2)的增加！(1, 2, 4, 8)\n*   使用这种方式时，一般往往是认为大多数情况下，存储的容量只有1个。\n*   `[NSMutableArray array]`更节约内存\n*   `capacity`：数组容量 -> 内存中的大小\n\n## 12 使用runtime证明KVO中生成了NSKVONotifying_Person\n```\n@implementation ViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    // Do any additional setup after loading the view.\n    \n    Person *p = [[Person alloc] init];\n    [p addObserver:self forKeyPath:@\"name\" options:NSKeyValueObservingOptionNew context:nil];\n    p.name = @\"Hamry\";\n    NSLog(@\"======%@\", [ViewController findSubClass:[p class]]);\n}\n\n+ (NSArray *)findSubClass:(Class)defaultClass {\n    // 注册类的总数\n    int count = objc_getClassList(NULL, 0);\n    // 创建一个数组，其中包含给定对象\n    NSMutableArray *array = [NSMutableArray arrayWithObject:defaultClass];\n    // 获取所有已注册的类\n    Class *classes = (Class *)malloc(sizeof(Class)*count);\n    objc_getClassList(classes, count);\n    // 遍历\n    for (int i = 0; i \u003C count; i++) {\n        if (defaultClass == class_getSuperclass(classes[i])) {\n            [array addObject:classes[i]];\n        }\n    }\n    free(classes);\n    return array;\n}\n\n- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary\u003CNSKeyValueChangeKey,id> *)change context:(void *)context {\n    NSLog(@\"change: %@\", change);\n}\n```\n\n```\n2019-07-23 11:56:06.303501+0800 MethodForwarding[66501:2118253] change: {\n    kind = 1;\n    new = Hamry;\n}\n2019-07-23 11:56:06.362926+0800 MethodForwarding[66501:2118253] ======(\n    Person,\n    \"NSKVONotifying_Person\"\n)\n\n```","iOS的kvo的底层实现原理，通过代码调试一步步来实现对kvo原理的探索。","",499668042977349,{"phone":15,"userId":13,"nickName":16,"vipType":17,"avatar":18,"sign":19,"createdAt":20},"13121171998","全栈老韩",1,"https://image.xinwei.ltd/images/IMG_5430.JPG","全栈工程师，擅长iOS App开发、前端（vue、react、nuxt、小程序&Taro）开发、Flutter、React Native、后端（midwayjs、golang、express、koa）开发、docker容器、seo优化等。","2024-01-01T16:14:30.305Z",2,{"id":21,"name":23},"IT技术",3,{"id":24,"name":26,"parentId":21},"iOS",[],59,0,"ios,ios kvo,ios调试,ios debug"]