OC底层原理(14)-- KVO 下 (自定义KVO进阶)

217 阅读9分钟

一、KVO 自定义 setter 函数

上篇中自定义 KVO 时,有个方法是在生成自定义子类时,对生成的子类中 setter 进行了实现,只做了一半,就是当外面对父类对象的属性值赋值时,调用setter 方法时其实就会来到动态子类的setter,在上篇自定义 KVO 的时候,因为lg_setter 方法里面什么也没做,通过打印可知并没有调用对象中的 setter 方法,但是我们平时肉眼看到的是,确实父类对象中的属性的值确实也发生了变化,所以底层在实现这个方法的时候肯定有做些什么了,于是就一起来看看动态子类中的 setter 到底做了什么了

static void lg_setter(id self,SEL _cmd,id newValue){
    NSLog(@"来了:%@",newValue);
    
    // 应该些什么? 内部 -> 相应点 newValue  oldValue option context  -> change
    // 回调给外界
    
}

还记得前一篇有提到 LGPerson 是 NSKVONotifying_LGPerson 的父类,那在这里是不是就是要把子类的消息发送给父类,要是消息的发送,这里便要用到 objc_msgSendSuper, 因为是子类与父类的关系,接下来我们来验证一下。

结合上一篇的自定义KVO,这里代码就不贴重复贴出了,只写做改动的部分

static void lg_setter(id self,SEL _cmd,id newValue){
    NSLog(@"来了:%@",newValue);
    
    //1:消息发送给父类方法
    struct objc_super superStruct = {
        .receiver = self,
        .super_class = class_getSuperclass([self class])
    };
    void (* lg_objc_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
    lg_objc_msgSendSuper(&superStruct, _cmd, newValue);

    
}

上篇有一点是,将 class 的实现重定义给了 lg_class

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

所以在 lg_setter 中的[self class] 实际上走的就是 lg_class 方法,lg_class 的self , 所以object_getClass 得到的就是动态子类 ,然后在通过动态子类得到它的父类,LGPerson,好将方法发送给父类

打印结构:

所以我们推测的流程是可以实现的,改变父类中属性的值

接下来就探索一下,当检测到属性的值改变后,或其中调用了 lg_setter 又是怎么回调的呢?于是我们又模拟一下观察的回调的方法的实现

代码实现:

NSObject+LGKVO.h 类中添加一个方法,并实现

- (void)lg_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object oldValue:(id)oldValue newValue:(id)newValue;

NSObject+LGKVO.m

#import "NSObject+LGKVO.h"
#import <objc/message.h>

static NSString *const kLGKVOPrefix = @"LGKVONotifying_";
static NSString *const kLGKVOAssiociateKey = @"kLGKVO_AssiociateKey";

@implementation NSObject (LGKVO)

- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
    
    //新添加步骤
    //4:保存观察者
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    // 1: 验证setter
    [self judgeSetterMethodFromKeyPath:keyPath];
    // 2: 动态生成子类
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    // 3: isa 指向 isa_swizzling
    object_setClass(self, newClass);
}
#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{
    
    // 2.1 判断是否有了
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];// LGKVONotifying_LGPerson
    Class newClass = NSClassFromString(newClassName);
    
    if (!newClass) {
         // 2.2 申请类
        newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
        // 2.3 注册类
        objc_registerClassPair(newClass);
        // 2.4.1 添加class方法
        SEL classSEL = NSSelectorFromString(@"class");
        Method classMethod = class_getClassMethod([self class], @selector(class));
        const char *classType = method_getTypeEncoding(classMethod);
        class_addMethod(newClass, classSEL, (IMP)lg_class, classType);
    }
    // 2.4.2 添加setter方法 setNickname
    // 判断一下
    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getClassMethod([self class], setterSEL);
    const char *setterType = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterType);
    
    return newClass;
}

static void lg_setter(id self,SEL _cmd,id newValue){
    NSLog(@"来了:%@",newValue);
    
    //0:取出旧值
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    id oldValue = [self valueForKey:keyPath];
    
    //1:消息发送给父类方法
    struct objc_super superStruct = {
        .receiver = self,
        .super_class = class_getSuperclass([self class])
    };
    void (* lg_objc_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
    lg_objc_msgSendSuper(&superStruct, _cmd, newValue);

    //2:KVO 观察到你有变化了, -> 回调信息   新添加步骤
    
    //取出观察者
    id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    
    SEL observerSEL = @selector(lg_observeValueForKeyPath:ofObject:oldValue:newValue:);
    objc_msgSend(observer, observerSEL, self, keyPath, oldValue, newValue);
}

Class lg_class(id self,SEL _cmd){
    return 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

验证:

LGViewController.m

- (void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object oldValue:(id)oldValue newValue:(id)newValue {
    NSLog(@"oldValue = %@ --- newValue = %@", oldValue,newValue);
}

打印结果:

注意:其中关联对象 objc_setAssociatedObject 的使用

//设置关联对象
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
 //获取关联对象    
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)

//移除
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)

自定义 KVO 的数据传输

上面说到的关联对象可以用来保存观察者,是可以的,在前面的运用中存一个,是没毛病,那同时观察多个的时候再用上面的方法,就有些狭隘了,一般我们在存多个相同类型的数据或对象时,我们会想到用数组,如果一个存储目标有多个属性时,就可以用模型或数组来赋值后,在用数组存储,接下来就实现一下

在 NSObject+LGKVO 里新添一个类,并实现初始化方法。

NSObject+LGKVO.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

typedef NS_OPTIONS(NSUInteger, LGKeyValueObservingOptions) {
    LGKeyValueObservingOptionsNew = 0x01,
    LGKeyValueObservingOptionsOld = 0x02,
};

@interface LGKVOInfo : NSObject

@property (nonatomic, strong) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) LGKeyValueObservingOptions options;

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

@end

@interface NSObject (LGKVO)

- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options context:(nullable void *)context;

- (void)lg_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object oldValue:(id)oldValue newValue:(id)newValue;;


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

@end

NSObject+LGKVO.m

#import "NSObject+LGKVO.h"
#import <objc/message.h>

static NSString *const kLGKVOPrefix = @"LGKVONotifying_";
static NSString *const kLGKVOAssiociateKey = @"kLGKVO_AssiociateKey";


@implementation LGKVOInfo : NSObject

- (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options {
    if (self = [super init]) {
        self.observer = observer;
        self.keyPath = keyPath;
        self.options = options;
    }
    return self;
}
@end

@implementation NSObject (LGKVO)

- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options context:(nullable void *)context{
    // 1: 验证setter
    [self judgeSetterMethodFromKeyPath:keyPath];
    // 2: 动态生成子类
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    // 3: isa 指向 isa_swizzling
    object_setClass(self, newClass);
    
    //4:保存观察者
    LGKVOInfo *info = [[LGKVOInfo alloc] initWithObserver:self forKeyPath:keyPath options:options];
    //用一个集合来收集
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    if (!mArray) {
        mArray = [NSMutableArray arrayWithCapacity:1];
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
     [mArray addObject:info];
}

#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{
    
    // 2.1 判断是否有了
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];// LGKVONotifying_LGPerson
    Class newClass = NSClassFromString(newClassName);
    
    if (!newClass) {
         // 2.2 申请类
        newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
        // 2.3 注册类
        objc_registerClassPair(newClass);
        // 2.4.1 添加class方法
        SEL classSEL = NSSelectorFromString(@"class");
        Method classMethod = class_getClassMethod([self class], @selector(class));
        const char *classType = method_getTypeEncoding(classMethod);
        class_addMethod(newClass, classSEL, (IMP)lg_class, classType);
    }
    // 2.4.2 添加setter方法 setNickname
    // 判断一下
    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getClassMethod([self class], setterSEL);
    const char *setterType = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterType);
    
    return newClass;
}

static void lg_setter(id self,SEL _cmd,id newValue){
    NSLog(@"来了:%@",newValue);
    
    //0:取出旧值
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    id oldValue = [self valueForKey:keyPath];
    
    //1:消息发送给父类方法
    struct objc_super superStruct = {
        .receiver = self,
        .super_class = class_getSuperclass([self class])
    };
    void (* lg_objc_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
    lg_objc_msgSendSuper(&superStruct, _cmd, newValue);

    //2:KVO 观察到你有变化了, -> 回调信息
    
    //取出观察者
    
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    for (LGKVOInfo *info in mArray) {
        if ([info.keyPath isEqualToString:keyPath]) {
            if (info.options & LGKeyValueObservingOptionsNew) {
                SEL observerSEL = @selector(lg_observeValueForKeyPath:ofObject:oldValue:newValue:);
                objc_msgSend(info.observer, observerSEL, self, keyPath, oldValue, newValue);
            }
        }
    }
}

Class lg_class(id self,SEL _cmd){
    return 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

到这里同时监听多个keyPath 进行缓存相关参数的问题,就解决了

LGViewController.m 部分代码

#pragma mark - KVO回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%@",change);
}

- (void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object oldValue:(id)oldValue newValue:(id)newValue {
    NSLog(@"oldValue = %@ --- newValue = %@", oldValue,newValue);
}


然后我们看到外面使用的地方LGViewController 里还要单独调个回调来得到参数,要是同时监听了几个方法,那在回调里还要做个判断,到底是那个keyPath的对调,然后针对这个问题,我们又进一步优化,可以利用Block来完成优化,见下一部分

自定义 KVO 的函数式编程思想

代码:

NSObject+LGKVO.h 添加一个回调,然后添加监听的方法也可以做一下修改

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

typedef NS_OPTIONS(NSUInteger, LGKeyValueObservingOptions) {
    LGKeyValueObservingOptionsNew = 0x01,
    LGKeyValueObservingOptionsOld = 0x02,
};

typedef void(^LGKVOBlock)(id observe, NSString *keyPath,  id oldValue, id newValue);

@interface LGKVOInfo : NSObject

@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) LGKeyValueObservingOptions options;
@property (nonatomic, copy) LGKVOBlock kvoBlock;

- (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options handle:(LGKVOBlock)handle;

@end

@interface NSObject (LGKVO)

- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handle:(LGKVOBlock)handle;

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

@end

NS_ASSUME_NONNULL_END

NSObject+LGKVO.m 部分代码

#import "NSObject+LGKVO.h"
#import <objc/message.h>

static NSString *const kLGKVOPrefix = @"LGKVONotifying_";
static NSString *const kLGKVOAssiociateKey = @"kLGKVO_AssiociateKey";


@implementation LGKVOInfo : NSObject

- (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handle:(nonnull LGKVOBlock)handle {
    if (self = [super init]) {
        _observer = observer;
        _keyPath = keyPath;
        _kvoBlock = handle;
    }
    return self;
}

@end

@implementation NSObject (LGKVO)

- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handle:(nonnull LGKVOBlock)handle {
    // 1: 验证setter
    [self judgeSetterMethodFromKeyPath:keyPath];
    // 2: 动态生成子类
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    // 3: isa 指向 isa_swizzling
    object_setClass(self, newClass);
    //4:保存观察者
    LGKVOInfo *info = [[LGKVOInfo alloc] initWithObserver:self forKeyPath:keyPath handle:handle];
    //用一个集合来收集
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    if (!mArray) {
        mArray = [NSMutableArray arrayWithCapacity:1];
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [mArray addObject:info];
}

static void lg_setter(id self,SEL _cmd,id newValue){
    NSLog(@"来了:%@",newValue);
    
    //0:取出旧值
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    id oldValue = [self valueForKey:keyPath];
    
    //1:消息发送给父类方法
    struct objc_super superStruct = {
        .receiver = self,
        .super_class = class_getSuperclass([self class])
    };
    void (* lg_objc_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
    lg_objc_msgSendSuper(&superStruct, _cmd, newValue);

    //2:KVO 观察到你有变化了, -> 回调信息
    
    //取出观察者
    
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    for (LGKVOInfo *info in mArray) {
        if ([info.keyPath isEqualToString:keyPath]) {
            info.kvoBlock(self, keyPath, oldValue, newValue);
        }
    }
}

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

NSObject+LGKVO.m 中需做以上修改

再贴出调用的地方

LGViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.person = [[LGPerson alloc] init];
    [self.person lg_addObserver:self forKeyPath:@"nickName" handle:^(id  _Nonnull observe, NSString * _Nonnull keyPath, id  _Nonnull oldValue, id  _Nonnull newValue) {
        NSLog(@"oldValue = %@ --- newValue = %@", oldValue,newValue);
    }];

}

这样当观察的属性值改变时就可以直接在 回调里,直接捕捉到变化了,并做相应的逻辑处理

自定义 KVO 的自动销毁

上一章有说KVO 的最后一步是将对象的isa 只会父类就是销毁,这一步是否放在 dealloc,要是NSObject 里面想要是想 dealloc 又要开始消息的转发来实现,流程就像上面setter的的转发一样,添加 lg_dealloc

NSObject+LGKVO.m

- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
    
    // 2.1 判断是否有了
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];// LGKVONotifying_LGPerson
    Class newClass = NSClassFromString(newClassName);
    
    if (newClass) return newClass;

    // 2.2 申请类
    newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
    // 2.3 注册类
    objc_registerClassPair(newClass);
    // 2.3.1 添加class方法
    SEL classSEL = NSSelectorFromString(@"class");
    Method classMethod = class_getClassMethod([self class], @selector(class));
    const char *classType = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, classSEL, (IMP)lg_class, classType);
    
    // 2.3.2 添加setter方法 setNickname
    // 判断一下
    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getClassMethod([self class], setterSEL);
    const char *setterType = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterType);
    
    // 2.3.3 : 添加dealloc
    SEL deallocSEL = NSSelectorFromString(@"dealloc");
    Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
    const char *deallocTypes = method_getTypeEncoding(deallocMethod);
    class_addMethod(newClass, deallocSEL, (IMP)lg_dealloc, deallocTypes);
    
    return newClass;
}

static void lg_dealloc(id self,SEL _cmd){ 
    Class superClass = [self class];//这里的 superClass原来的LGPerson类
    object_setClass(self, superClass);//将当前对象指回 LGPerson
}

在 lg_dealloc 中将 isa的指回去,就大概完成了KVO整个流程