美文网首页
iOS-自定义KVO

iOS-自定义KVO

作者: 似水流年_9ebe | 来源:发表于2021-08-01 02:38 被阅读0次

前言

iOS-KVO原理分析这篇文章我们分析了KVO的原理,接下来,我们自己动手简单实现一个KVO。

准备工作

首先建一个RoKVO的工程,再建一个RoViewController,在storyboard中关联起来,如图:


1

KVO自定义思路

我们知道KVO是通过添加NSObject的分类的实现,我们也模仿一下,新建一个NSObject+RoKVO的分类文件,然后我们再模护系统的添加和移除观察的观察的方法。如下:

- (void)ro_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;

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

思路如下:

  1. 判断setter方法不让实例进来。

pragma mark - 验证是否存在setter方法

- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
    Class superClass    = object_getClass(self);
    SEL setterSeletor   = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
    if (!setterMethod) {
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"没有当前%@的setter",keyPath] userInfo:nil];
    }
}

ro_addObserver这个方法中进行判断。

  1. KVO 核心:isa_siwzilling
    1. 1 申请类
    2. 2 添加方法
    3. 3 注册类
  2. isa指向交换
  3. setter 业务逻辑

动态生成子类

ro_addObserver 调用*- (Class)createChildClassWithKeyPath:(NSString )keyPath创建子类,后面会详细讲这个函数。
接着进行isa交换,代码如下:

object_setClass(self, newClass);

我们再讲createChildClassWithKeyPath这个核心功能。
首先我们要先申请类,调用* *这个API。
代码如下:

NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",roKVOPrefix,oldClassName];
    Class newClass = NSClassFromString(newClassName);
    // 判断是否存在,存在直接返回
    if (newClass) return newClass;
    // 创建类
    objc_allocateClassPair([self class],newClassName.UTF8String, 0);

接着我们去注册类,如下

 objc_registerClassPair(newClass);

接着我们再讲下这个方法的核心功能,添加方法,如下:

- (Class)createChildClassWithKeyPath:(NSString *)keyPath {
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",roKVOPrefix,oldClassName];
    Class newClass = NSClassFromString(newClassName);
    // 判断是否存在,存在直接返回
    if (newClass) return newClass;
    // 创建类
    newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
    
    // 添加方法 setter
    SEL setterSel = NSSelectorFromString(setterForGetter(keyPath));
    Method  setterMethod = class_getInstanceMethod([self class], setterSel);
    const char *setterType = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSel, (IMP)ro_setter, setterType);
    
    // 添加方法 class
    SEL classSel = NSSelectorFromString(@"class");
    Method  classMethod = class_getClassMethod([self class], classSel);
    const char *classType = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, setterSel, (IMP)ro_class, classType);
    
    // 注册类
    objc_registerClassPair(newClass);
    return newClass;
}

自定义KVO的setter

子类动态创建完了,我们来看下setter方法,如何重写。
由于篇幅过长,我们这里直接发代码,代码会有相关的注释。
如下:

static void ro_setter(id self,SEL _cmd,id newValue) {
    // 往父类发送消息
    struct objc_super ro_objc_super;
    ro_objc_super.receiver = self;
    ro_objc_super.super_class = class_getSuperclass(object_getClass(self));
    
    void (*ro_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
    ro_msgSendSuper(&ro_objc_super,_cmd,newValue);
    
    // 既然观察到了,下一步是回调,接着让我们的观察者调用
    //拿到观察者
    id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(RoKVOAssiociateKey));
    
    // 2: 消息发送给观察者
    SEL observerSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);
}

自定义KVO的多元素观察和移除观察

当我们需要观察多个元素的时候,要怎么处理呢,接下来我们来看下。
我们添加了一个RoKVOInfo用来存储观察KVO的数据的存储(代码会在结尾发出来)。
如何观察多元素呢,如ro_addObserver代码:

- (void)ro_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
    if (keyPath.length ==0 ) {
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"keyPath不能为空"] userInfo:nil];
    }
    // 判断setter方法是否存在
    [self judgeSetterMethodFromKeyPath:keyPath];
    // 动态生成子类
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    
    // isa指针交换
    object_setClass(self, newClass);
    
    //保存KVO信息,收集信息
    RoKVOInfo *info = [[RoKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:(RoKeyValueObservingOptions)options];
    NSMutableArray *array = objc_getAssociatedObject(self,(__bridge const void *)(roKVOAssiociateKey));
    if (!array) {
        array = [NSMutableArray arrayWithCapacity:1];
    }
    [array addObject:info];
    objc_setAssociatedObject(self, (__bridge const void *)(roKVOAssiociateKey), array, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

ro_setter的代码:

static void ro_setter(id self,SEL _cmd,id newValue) {
    // 通过cmd(sel)获取keyPath
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    id oldValue       = [self valueForKey:keyPath];
    // 往父类发送消息
    struct objc_super ro_objc_super;
    ro_objc_super.receiver = self;
    ro_objc_super.super_class = class_getSuperclass(object_getClass(self));
    
    void (*ro_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
    ro_msgSendSuper(&ro_objc_super,_cmd,newValue);
    
 
    NSMutableArray *array = objc_getAssociatedObject(self,
                                                    (__bridge  const void *)roKVOAssiociateKey);
    // 遍历查找是setter方法在观察
    for (RoKVOInfo *info in array) {
        if ([info.keyPath isEqualToString:keyPath]) {
            dispatch_async(dispatch_queue_create(0, 0), ^{
                NSMutableDictionary<NSKeyValueChangeKey, id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
                // 对新旧值进处处理
                if (info.options  & RoKeyValueObservingOptionNew) {
                    [change setObject:newValue forKey:NSKeyValueChangeNewKey];
                }
                if (info.options & RoKeyValueObservingOptionOld) {
                    [change setObject:@"" forKey:NSKeyValueChangeOldKey];
                    if (oldValue) {
                        [change setObject:oldValue forKey:NSKeyValueChangeOldKey];
                    }
                }
                void (*ro_objc_msgSend)(NSObject *observer,SEL , NSString *keyPath, id, NSMutableDictionary *change, void *context) = (void *)objc_msgSend;
                // 消息发送给观察者
                SEL observerSel = @selector(ro_observeValueForKeyPath:ofObject:change:context:);
                ro_objc_msgSend(info.observer,observerSel,keyPath,self,change,NULL);
                
            });
        }
    }
}

那么我们该如何移除我们的观察呢?
我们知道,KVO在移除观察时,会交换isa,那么我们模访官方的做法,如下代码:

- (void)ro_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
    NSMutableArray *array = objc_getAssociatedObject(self,(__bridge const void *)(roKVOAssiociateKey));
    if (array.count <=0) {
        return;
    }
    for (RoKVOInfo *info in array) {
        if ([info.keyPath isEqualToString:keyPath]) {
            [array removeObject:info];
            objc_setAssociatedObject(self, (__bridge const void *)(roKVOAssiociateKey), array, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            break;
        }
    }
    
    if (array.count <=0) {
        Class superClass = [self class];
        object_setClass(self, superClass);
    }
}

自定义KVO的函数式

我们知道KVO观察写的代码要多,一个回调会有很多if else的这种结构,那么我们怎么优化呢,当然可以通过函数式编程要优化,我们可以通过block实现。

  1. 定义一个block *typedef void(^RoKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);
  1. 这个block需要存储在RoKVOInfo中。
  2. 发送消息的时候,直接使用block传数据。
    函数式是映射数学上的函数概念,是定义域映射到值域上的关系,函数可以做为参数传参,也可以作为返回值

自定义KVO自动销毁

我们知道KVO是不会自动销毁的,需要手动销毁,我们实现了KVO的函数式,那么我们可以实现自动销毁吗?答案 是肯定的。下面我们来实现下。
流程:

  1. 重写dealloc方法
    2.在重写的dealloc方法中释放。

结语

以个就是个在实现的一个简单的KVO,还有很多不完善的地方,后续会发出来一个比较完整的自定义的KVO。

附上KVO的代码

RoKVOInfo.h文件代码

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

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


@interface RoKVOInfo : NSObject
@property (nonatomic, weak) NSObject  *observer;
@property (nonatomic, copy) NSString    *keyPath;
@property (nonatomic, copy) RoKVOBlock  block;

- (instancetype)initWitObserver:(NSObject *)observer
                     forKeyPath:(NSString *)keyPath
                          block:(RoKVOBlock)block;
@end

NS_ASSUME_NONNULL_END

RoKVOInfo.m文件代码

#import "RoKVOInfo.h"

@implementation RoKVOInfo
- (instancetype)initWitObserver:(NSObject *)observer
                     forKeyPath:(NSString *)keyPath
                          block:(RoKVOBlock)block {
    self = [super init];
    if (self) {
        _observer = observer;
        _keyPath  = keyPath;
        _block = block;
    }
    return self;
}
@end

NSObject+RoKVO.h文件代码

#import <Foundation/Foundation.h>
#import "RoKVOInfo.h"
NS_ASSUME_NONNULL_BEGIN


@interface NSObject (RoKVO)

- (void)ro_addObserver:(NSObject *)observer
            forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
                 block:(RoKVOBlock)block;


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

@end

NS_ASSUME_NONNULL_END

NSObject+RoKVO.m文件代码

#import "NSObject+RoKVO.h"
#import <objc/message.h>
static NSString *const roKVOPrefix = @"ROKVONotifying_"; //动态子类的前缀
static NSString *const roKVOAssiociateKey = @"ROKVO_AssiociateKey";

@implementation NSObject (RoKVO)



- (void)ro_addObserver:(NSObject *)observer
            forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
                 block:(RoKVOBlock)block {
    if (keyPath.length ==0 ) {
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"keyPath不能为空"] userInfo:nil];
    }
    [self judgeSetterMethodFromKeyPath:keyPath];
    // 动态生成子类
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    // isa的指向 : LGKVONotifying_LGPerson
    object_setClass(self, newClass);
    // 保存信息
    RoKVOInfo *info = [[RoKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath block:block];
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(roKVOAssiociateKey));
    if (!mArray) {
        mArray = [NSMutableArray arrayWithCapacity:1];
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(roKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [mArray addObject:info];
}


- (void)ro_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(roKVOAssiociateKey));
    if (observerArr.count<=0) {
        return;
    }
    
    for (RoKVOInfo *info in observerArr) {
        if ([info.keyPath isEqualToString:keyPath]) {
            [observerArr removeObject:info];
            objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(roKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            break;
        }
    }
    
    if (observerArr.count<=0) {
        // 指回给父类
        Class superClass = [self class];
        object_setClass(self, superClass);
    }
}

#pragma mark - 验证是否存在setter方法
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
    Class superClass    = object_getClass(self);
    SEL setterSeletor   = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
    if (!setterMethod) {
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"没有当前%@的setter",keyPath] userInfo:nil];
    }
}

#pragma mark - 动态创建子类
- (Class)createChildClassWithKeyPath:(NSString *)keyPath {
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",roKVOPrefix,oldClassName];
    Class newClass = NSClassFromString(newClassName);
    // 防止重复创建生成新类
    if (newClass) return newClass;
    
    // 申请类
    newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
    // 注册类
    objc_registerClassPair(newClass);
    // 添加class
    SEL classSEL = NSSelectorFromString(@"class");
    Method classMethod = class_getInstanceMethod([self class], classSEL);
    const char *classTypes = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, classSEL, (IMP)ro_class, classTypes);
    //  添加setter
    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod([self class], setterSEL);
    const char *setterTypes = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)ro_setter, setterTypes);
    //  添加dealloc
    SEL deallocSEL = NSSelectorFromString(@"dealloc");
    Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
    const char *deallocTypes = method_getTypeEncoding(deallocMethod);
    class_addMethod(newClass, deallocSEL, (IMP)ro_dealloc, deallocTypes);
 
    return newClass;
    
}

static void ro_dealloc(id self,SEL _cmd){
    Class superClass = [self class];
    object_setClass(self, superClass);
}

#pragma mark - setter
static void ro_setter(id self,SEL _cmd,id newValue){
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    id oldValue = [self valueForKey:keyPath];
    // 消息转发 转发给父类
    // 改变父类的值 --- 可以强制类型转换
    void (*ro_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
    struct objc_super superStruct = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self)),
    };
    //objc_msgSendSuper(&superStruct,_cmd,newValue)
    ro_msgSendSuper(&superStruct,_cmd,newValue);
    
    // 信息数据回调
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(roKVOAssiociateKey));
    
    for (RoKVOInfo *info in mArray) {
        if ([info.keyPath isEqualToString:keyPath] && info.block) {
            info.block(info.observer, keyPath, oldValue, newValue);
        }
    }
}

Class ro_class(id self,SEL _cmd){
    return class_getSuperclass(object_getClass(self));
}

#pragma mark - 从get方法获取set方法的名称 key ===>>> setKey:
static NSString *setterForGetter(NSString *getter){
    
    if (getter.length <= 0) { return nil;}
    
    NSString *firstString = [[getter substringToIndex:1] uppercaseString];
    NSString *leaveString = [getter substringFromIndex:1];
    
    return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}

#pragma mark - 从set方法获取getter方法的名称 set<Key>:===> key
static NSString *getterForSetter(NSString *setter){
    
    if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
    
    NSRange range = NSMakeRange(3, setter.length-4);
    NSString *getter = [setter substringWithRange:range];
    NSString *firstString = [[getter substringToIndex:1] lowercaseString];
    return  [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
}

@end

相关文章

  • iOS - KVO

    [toc] 参考 KVO KVC 【 iOS--KVO的实现原理与具体应用 】 【 IOS-详解KVO底层实现 】...

  • iOS-自定义KVO

    当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属...

  • iOS-自定义KVO

    KVO原理及使用 我们之前讨论过KVO的原理,知道KVO机制是生成了一个中间类NSKVONotifying,该中间...

  • IOS-自定义KVO

    本文首发于 个人博客 之前写了一篇 kvo原理探索,今天我们来自己动手实现它的逻辑,这样我们对kvo的认识会更深刻...

  • iOS-自定义KVO

    前言 iOS-KVO原理分析[https://www.jianshu.com/p/f94a972f6187]这篇文...

  • iOS-底层原理21-KVO(下)

    iOS-底层原理21-KVO(下) 《iOS底层原理文章汇总》[https://www.jianshu.com/p...

  • iOS runtime自定义实现KVO

    1、了解KVO 打印结果: 2、自定义实现KVO .h .m

  • KVO基本使用

    分三部分解释KVO一.KVO基本使用二.KVO原理解析三.自定义实现KVO 一、KVO基本使用 使用KVO,能够非...

  • KVC/ KVO

    1、kvc原理: 45页 2、自定义KVO KVO参考链接 KVO默认观察setter,使用isa-swizzli...

  • iOS-底层原理-自定义KVO

    1.自定义KVO 1.上一篇博客了解了iOS 系统KVO的底层实现原理,那么这里进行自定义KVO,更好的理解原理和...

网友评论

      本文标题:iOS-自定义KVO

      本文链接:https://www.haomeiwen.com/subject/lmhtvltx.html