• 自定义KVO (二)


    系统dealloc中做了什么事情?

    移除观察者之前打印: 

    移除观察者,isa指针指回原来的类,动态生成的子类不知道是否会消失?打印下:

    (防止isa的影响,在动态方法解析的时候,要判断class.isa 是否为 metal class,类的isa不一定指向metal,有可能指向kvo动态子类,所以是为了排除动态子类的中间影响)

     动态子类没有消失:在添加观察者是已经做了很多操作(申请内存,开辟空间,注册到内存中等),这是如果后面又需要观察,就不需要重新创建(空间换时间 ) ,是不会消失的。

    注册的表为getobjc_class, 在map_images的时候,这些类在整个应用程序重新刷新的的时候,这张表会刷新(缓存的重新刷新 ),例如应用程序进入后台多少秒之后等。

    添加移除观察者代码:

    - (void)fx_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{

        //指回父类(把isa指回来 )

        Class superClass = [self class];//KVOStudent

        objc_setClass(self, superClass);

    }

     

    单步调试打印:

     

    已指回原来的类,移除观察者成功 。

    实现添加保存多个观察者:

    初步想法是用集合来保存,每次都add一个对象,或者使用map来添加

    尝试封装一个内部对象:模仿系统KVO

    //

    //  FXKVOInfo.h

    //  fxDemo

    //

    //  Created by 樊星 on 2019/12/27.

    //  Copyright © 2019 樊星. All rights reserved.

    //

     

    #import <Foundation/Foundation.h>

     

    typedef NS_OPTIONS(NSUInteger, FXKeyValueObservingOptions) {

     

        /* Whether the change dictionaries sent in notifications should contain NSKeyValueChangeNewKey and NSKeyValueChangeOldKey entries, respectively.

        */

        FXKeyValueObservingOptionNew = 0x01,

        FXKeyValueObservingOptionOld = 0x02,

    };

     

    NS_ASSUME_NONNULL_BEGIN

     

    @interface FXKVOInfo : NSObject

     

    @property (nonatomic, copy) NSString *keyPath;

    @property (nonatomic, strong) NSObject *observer;

    @property (nonatomic, assign) FXKeyValueObservingOptions options;

     

    - (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(FXKeyValueObservingOptions)options;

    @end

     

    NS_ASSUME_NONNULL_END

    然后在添加KVO观察者时,添加到数组里面,实现可以添加多观察者:

    - (void)fx_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(FXKeyValueObservingOptions)options context:(nullable void *)context{

        //1. 验证set方法

        [self judgeSetterMethodFromKeyPath:keyPath];

        //2. 动态生成子类

        Class newClass = [self creatChildClass:keyPath];

        //3. 当前对象的类,isa指向newClass

        object_setClass(self, newClass);

        //4. 保存KVO信息

        //集合 --> add map

        FXKVOInfo *info = [[FXKVOInfo alloc] initWithObserver:observer forKeyPath:keyPath options:options];

        NSMutableArray *infoArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(FXKVOAssiociateKey));

        if(!infoArray) {

          //数组 -> 成员 强引用

            //self(vc) -> person ISA -> 数组 -> info -/weak/-> self(VC) ?

            //self.person -> FXKVO -> //内存问题,这里为什么没有形成循环引用?

            infoArray = [NSMutableArray arrayWithCapacity:1];

            objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(FXKVOAssiociateKey), infoArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

        }

        [infoArray addObject:info];

    }

     

    调用set方法时遍历观察者数组:

    //响应回调 -- KVOVC(观察者) --调用某个方法 -- obbser

        //把观察者存起来,先用属性存起来(关联存储)

    //    id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(FXKVOAssiociateKey));

        //响应

        //sel

        NSMutableArray *infoArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(FXKVOAssiociateKey));

        for (FXKVOInfo *info in infoArray) {

            if([info.keyPath isEqualToString:keyPath]){

                dispatch_async(dispatch_get_global_queue(0, 0), ^{

                    //枚举--新值+旧值

                    NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionaryWithCapacity:1];

                    //新值 0x0001 代表新值

                    // &0x0001, 如果都相同则为1

                    if (info.options & FXKeyValueObservingOptionNew) {

                        [change setObject:newValue?:@"" forKey:NSKeyValueChangeNewKey];

                    }

                    if (info.options & FXKeyValueObservingOptionOld) {

                        [change setObject:oldValue?:@"" forKey:NSKeyValueChangeOldKey];

                    }

                    SEL obserSEL = @selector(observeValueForKeyPath:ofObject:change:context:);

                    ((id (*)(id, SEL, id, id, id, id))objc_msgSend)(info.observer, obserSEL, keyPath, self, change, NULL);

                });

            }

        }

     

    目前还存在的问题是添加观察者和调用观察者方法的逻辑代码和功能代码的分离,下面实现链式添加观察者和调用观察者(用block实现):

    @interface NSObject (FXKVO)

     

    typedef void(^FXKVOBlock)(NSObject *observer, NSString *keyPath, id oldValue, id newValue);

     

    - (void)fx_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(FXKeyValueObservingOptions)options context:(nullable void *)context handBlock:(FXKVOBlock)handBlock;

     

    - (void)fx_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;

     

    - (void)fx_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

    @end

     

    添加观察者修改:不需要在重新写方法来提供回调

      [self.student fx_addObserver:self forKeyPath:@"name" options:FXKeyValueObservingOptionOld|FXKeyValueObservingOptionNew context:NULL handBlock:^(NSObject * _Nonnull observer, NSString * _Nonnull keyPath, id  _Nonnull oldValue, id  _Nonnull newValue) {

            

        }];

     

    移除观察者逻辑:

    - (void)fx_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{

        

        NSMutableArray *infoArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(FXKVOAssiociateKey));

        [infoArray enumerateObjectsUsingBlock:^(FXKVOInfo *info, NSUInteger idx, BOOL * _Nonnull stop) {

            if([info.keyPath isEqualToString:keyPath]

               &&(observer == info.observer)) {

                [infoArray removeObject:info];

                *stop = YES;

            }

        }];

        //如果关联数组没有KVO信息->清空

        if(infoArray.count == 0){

            objc_removeAssociatedObjects(infoArray);

        }

        //指回父类

        Class superClass = [self class];//KVOStudent

        object_setClass(self, superClass);

    }

  • 相关阅读:
    mysql8.0.21下载安装详细教程
    ORDER BY 高级用法之CASE WHEN继续研究
    前端实用在线小工具推荐
    从nodejs的AES加密解密之后文件大小不一致的问题谈谈AES加密中的补位
    纯前端如何实现多语言切换
    [React] React Virtual
    [Kotlin] Compare Functional Programming in Java and Kotlin
    [Kotlin] Catch Error in Java
    [Angular] Saving draft form into Cookies
    [Angular] Data Resolver
  • 原文地址:https://www.cnblogs.com/coolcold/p/12084921.html
Copyright © 2020-2023  润新知