Key-Value Observing(kvo)二:自定义kvo

一、自定义kvo

在上篇文章 kvo原理分析 中分析了系统kvo原来,在这个章节将实现一个简单的kvo
那么就有以下方法:

@interface NSObject (HP_KVO)

- (void)hp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)hp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
- (void)hp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

@end

1.1 hp_addObserver

1.1.1参数检查

- (void)hp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
    //1.参数判断 以及 setter检查
    if (!observer || !keyPath) return;
    BOOL result = [self handleSetterMethodFromKeyPath:keyPath];
    if (!result) return;
}

- (BOOL)handleSetterMethodFromKeyPath:(NSString *)keyPath {
    SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod(object_getClass(self), setterSeletor);
    NSAssert(setterMethod, @"%@ setter is not exist",keyPath);
    return setterMethod ? YES : NO;
}

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

由于只有属性才有效,所以先进行容错处理。

1.1.2 isa_swizzle动态生成子类

static NSString *const kHPKVOClassPrefix = @"HPKVONotifying_";

//申请类-注册类-添加方法
- (Class)creatKVOClassWithKeyPath:(NSString *)keyPath {
    //这里重写class后kvo子类也返回的是父类的名字
    NSString *superClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",kHPKVOClassPrefix,superClassName];
    Class newClass = NSClassFromString(newClassName);
    //类是否存在
    if (!newClass)  {//不存在需要创建类
        //1:申请类 父类、新类名称、额外空间
        newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
        //2:注册类
        objc_registerClassPair(newClass);
        //3:添加class方法,class返回父类信息 这里是`-class`
        SEL classSEL = NSSelectorFromString(@"class");
        Method classMethod = class_getInstanceMethod([self class], classSEL);
        const char *classTypes = method_getTypeEncoding(classMethod);
        class_addMethod(newClass, classSEL, (IMP)hp_class, classTypes);
    }
    //4:添加setter方法
    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod([self class], setterSEL);
    const char *setterTypes = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)hp_setter, setterTypes);
    
    return newClass;
}

//返回父类信息
Class hp_class(id self,SEL _cmd){
    return class_getSuperclass(object_getClass(self));
}

static void hp_setter(id self,SEL _cmd,id newValue){
    
}
  • 根据类名字拼接kvo类名字,判断是否已经存在。(superClassName由于class重写了,即使二次进入也获取到的是父类的名字)。
  • newClass不存在则调用objc_allocateClassPair创建kvo子类。并且重写- class方法。
  • 添加对应的setter方法。

当然也可以写+class,写入元类中。在objc_allocateClassPair后元类就存在了:

image.png

1.1.3 isa 指向子类

object_setClass(self, newClass);
  • 直接调用object_setClass设置objisa为新创建的kvo子类。

object_setClass源码:

Class object_setClass(id obj, Class cls)
{
    if (!obj) return nil;
    if (!cls->isFuture()  &&  !cls->isInitialized()) {
        lookUpImpOrNilTryCache(nil, @selector(initialize), cls, LOOKUP_INITIALIZE);
    }

    return obj->changeIsa(cls);
}

源码中就是修改对象的isa指向。

1.1.4 setter逻辑

在进行了上面逻辑的处理后,这个时候调用如下代码:

[self.obj hp_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
self.obj.name = @"HP";

会进入hp_setter函数。目前从HPObjectsetterName替换到了HPKVONotifying_ HPObjecthp_setter函数中。

hp_setter主要逻辑分两部分:调用父类方法以及发送通知

static void hp_setter(id self,SEL _cmd,id newValue) {
    //自动开关判断,省略
    //1.调用父类的setter(也可以通过performSelector调用)
    void (*hp_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
    struct objc_super super_struct = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self)),
    };
    hp_msgSendSuper(&super_struct,_cmd,newValue);
    
    //2.通知观察者
    [observer hp_observeValueForKeyPath:getterForSetter(_cmd) ofObject:self change:@{} context:NULL];
}
  • 调用父类方法可以通过objc_msgSendSuper实现。
  • 通知观察者keypath可以通过_cmd转换获取,objectselfchange也可以获取到,context可以先不传。那么核心就是observer的获取。

通知观察者
首先想到的是用属性存储observer,那么有个问题在类已经创建后就无法添加了。所以关联属性明显更合适。在hp_addObserver中添加关联对象:

static NSString *const kHPKVOAssiociateKey = @"HPKVOAssiociateKey";

objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kHPKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

通知逻辑实现:

//2.通知观察者
id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHPKVOAssiociateKey));
[observer hp_observeValueForKeyPath:getterForSetter(NSStringFromSelector(_cmd)) ofObject:self change:@{@"kind":@1,@"new":newValue} context:NULL];

//获取getter
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];
}

这个时候在hp_observeValueForKeyPath中就有回调了:

change:{
    kind = 1;
    new = HP;
}

1.1.5 观察者信息保存

上面的逻辑虽然简单实现了,但是存在一个严重问题,观察多个属性的时候以及新旧值都要观察以及传递了context的情况就无效了。
那么就需要保存观察者相关的信息,创建一个新类HPKVOInfo实现如下:

typedef NS_OPTIONS(NSUInteger, HPKeyValueObservingOptions) {
    HPKeyValueObservingOptionNew = 0x01,
    HPKeyValueObservingOptionOld = 0x02,
};

@interface HPKVOInfo : NSObject

@property (nonatomic, weak) id observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) HPKeyValueObservingOptions options;
@property (nonatomic, strong) id context;

- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(HPKeyValueObservingOptions)options context:(nullable void *)context;

@end

@implementation HPKVOInfo

- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(HPKeyValueObservingOptions)options context:(nullable void *)context {
    self = [super init];
    if (self) {
        self.observer = observer;
        self.keyPath  = keyPath;
        self.options  = options;
        self.context = (__bridge id _Nonnull)(context);
    }
    return self;
}

@end

hp_addObserver中信息保存修改如下:

//保存观察者信息-数组
HPKVOInfo *kvoInfo = [[HPKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options context:context];
NSMutableArray *observerArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHPKVOAssiociateKey));
if (!observerArray) {
    observerArray = [NSMutableArray arrayWithCapacity:1];
}
[observerArray addObject:kvoInfo];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kHPKVOAssiociateKey), observerArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

hp_setter逻辑修改如下:

static void hp_setter(id self,SEL _cmd,id newValue) {
    //自动开关判断,省略
    //保存旧值
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    id oldValue = [self valueForKey:keyPath];
    //1.调用父类的setter(也可以通过performSelector调用)
    void (*hp_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
    struct objc_super super_struct = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self)),
    };
    hp_msgSendSuper(&super_struct,_cmd,newValue);
    
    //2.通知观察者
    NSMutableArray *observerArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHPKVOAssiociateKey));
    for (HPKVOInfo *info in observerArray) {//循环调用,可能添加多次。
        if ([info.keyPath isEqualToString:keyPath]) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                NSMutableDictionary *change = [NSMutableDictionary dictionaryWithCapacity:1];
                //对新旧值进行处理
                if (info.options & HPKeyValueObservingOptionNew) {
                    [change setObject:newValue forKey:NSKeyValueChangeNewKey];
                }
                if (info.options & HPKeyValueObservingOptionOld) {
                    if (oldValue) {
                        [change setObject:oldValue forKey:NSKeyValueChangeOldKey];
                    } else {
                        [change setObject:@"" forKey:NSKeyValueChangeOldKey];
                    }
                }
                [change setObject:@1 forKey:@"kind"];
                //消息发送给观察者
                [info.observer hp_observeValueForKeyPath:keyPath ofObject:self change:change context:(__bridge void * _Nullable)(info.context)];
            });
        }
    }
}
  • 在调用父类之前先获取旧值。
  • 取出关联对象数组数据,循环判断调用hp_observeValueForKeyPath通知观察者。

这个时候观察多个属性以及多次观察就都没问题了。

1.2 hp_removeObserver

观察者对象是保存在关联对象中,所以在移除的时候也需要删除关联对象,并且当没有观察者时就要回复isa指向了。

- (void)hp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
    [self hp_removeObserver:observer forKeyPath:keyPath context:NULL];
}

- (void)hp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context {
    NSMutableArray *observerArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHPKVOAssiociateKey));
    if (observerArray.count <= 0) {
        return;
    }
    
    NSMutableArray *tempArray = [observerArray mutableCopy];
    for (HPKVOInfo *info in tempArray) {
        if ([info.keyPath isEqualToString:keyPath]) {
            if (info.observer) {
                if (info.observer == observer) {
                    if (context != NULL) {
                        if (info.context == context) {
                            [observerArray removeObject:info];
                        }
                    } else {
                        [observerArray removeObject:info];
                    }
                }
            } else {
                if (context != NULL) {
                    if (info.context == context) {
                        [observerArray removeObject:info];
                    }
                } else {
                    [observerArray removeObject:info];
                }
            }
        }
    }
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kHPKVOAssiociateKey), observerArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //已经全部移除了
    if (observerArray.count <= 0) {
        //isa指回给父类
        Class superClass = [self class];
        object_setClass(self, superClass);
    }
}
  • 通过keyPath以及observercontext确定要移除的关联对象数据。
  • 当关联对象中没有数据的时候isa进行指回。

完整代码如下:

@interface NSObject (HP_KVO)

- (void)hp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(HPKeyValueObservingOptions)options context:(nullable void *)context;
- (void)hp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
- (void)hp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

- (void)hp_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary *)change context:(nullable void *)context;

@end

#import "NSObject+HP_KVO.h"
#import 
#import 

static NSString *const kHPKVOClassPrefix = @"HPKVONotifying_";
static NSString *const kHPKVOAssiociateKey = @"HPKVOAssiociateKey";

@implementation NSObject (HP_KVO)

- (void)hp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(HPKeyValueObservingOptions)options context:(nullable void *)context {
    //1.参数判断 以及 setter检查
    if (!observer || !keyPath) return;
    BOOL result = [self handleSetterMethodFromKeyPath:keyPath];
    if (!result) return;
    
    //2.isa_swizzle 申请类-注册类-添加方法
    Class newClass = [self creatKVOClassWithKeyPath:keyPath];
    
    //3.isa 指向子类
    object_setClass(self, newClass);
    //4.setter逻辑处理
    //保存观察者信息-数组
    HPKVOInfo *kvoInfo = [[HPKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options context:context];
    NSMutableArray *observerArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHPKVOAssiociateKey));
    if (!observerArray) {
        observerArray = [NSMutableArray arrayWithCapacity:1];
    }
    [observerArray addObject:kvoInfo];
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kHPKVOAssiociateKey), observerArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)hp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
    [self hp_removeObserver:observer forKeyPath:keyPath context:NULL];
}

- (void)hp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context {
    NSMutableArray *observerArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHPKVOAssiociateKey));
    if (observerArray.count <= 0) {
        return;
    }
    
    NSMutableArray *tempArray = [observerArray mutableCopy];
    for (HPKVOInfo *info in tempArray) {
        if ([info.keyPath isEqualToString:keyPath]) {
            if (info.observer) {
                if (info.observer == observer) {
                    if (context != NULL) {
                        if (info.context == context) {
                            [observerArray removeObject:info];
                        }
                    } else {
                        [observerArray removeObject:info];
                    }
                }
            } else {
                if (context != NULL) {
                    if (info.context == context) {
                        [observerArray removeObject:info];
                    }
                } else {
                    [observerArray removeObject:info];
                }
            }
        }
    }
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kHPKVOAssiociateKey), observerArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //已经全部移除了
    if (observerArray.count <= 0) {
        //isa指回给父类
        Class superClass = [self class];
        object_setClass(self, superClass);
    }
}

- (BOOL)handleSetterMethodFromKeyPath:(NSString *)keyPath {
    SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod(object_getClass(self), setterSeletor);
    NSAssert(setterMethod, @"%@ setter is not exist",keyPath);
    return setterMethod ? YES : NO;
}

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

//申请类-注册类-添加方法
- (Class)creatKVOClassWithKeyPath:(NSString *)keyPath {
    //这里重写class后kvo子类也返回的是父类的名字
    NSString *superClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",kHPKVOClassPrefix,superClassName];
    Class newClass = NSClassFromString(newClassName);
    //类是否存在
    if (!newClass)  {//不存在需要创建类
        //1:申请类 父类、新类名称、额外空间
        newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
        //2:注册类
        objc_registerClassPair(newClass);
        //3:添加class方法,class返回父类信息 这里是`-class`
        SEL classSEL = NSSelectorFromString(@"class");
        Method classMethod = class_getInstanceMethod([self class], classSEL);
        const char *classTypes = method_getTypeEncoding(classMethod);
        class_addMethod(newClass, classSEL, (IMP)hp_class, classTypes);
    }
    //4:添加setter方法
    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod([self class], setterSEL);
    const char *setterTypes = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)hp_setter, setterTypes);
    
    return newClass;
}

//返回父类信息
Class hp_class(id self,SEL _cmd) {
    return class_getSuperclass(object_getClass(self));
}

static void hp_setter(id self,SEL _cmd,id newValue) {
    //自动开关判断,省略
    //保存旧值
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    id oldValue = [self valueForKey:keyPath];
    //1.调用父类的setter(也可以通过performSelector调用)
    void (*hp_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
    struct objc_super super_struct = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self)),
    };
    hp_msgSendSuper(&super_struct,_cmd,newValue);
    
    //2.通知观察者
    NSMutableArray *observerArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHPKVOAssiociateKey));
    for (HPKVOInfo *info in observerArray) {//循环调用,可能添加多次。
        if ([info.keyPath isEqualToString:keyPath]) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                NSMutableDictionary *change = [NSMutableDictionary dictionaryWithCapacity:1];
                //对新旧值进行处理
                if (info.options & HPKeyValueObservingOptionNew) {
                    [change setObject:newValue forKey:NSKeyValueChangeNewKey];
                }
                if (info.options & HPKeyValueObservingOptionOld) {
                    if (oldValue) {
                        [change setObject:oldValue forKey:NSKeyValueChangeOldKey];
                    } else {
                        [change setObject:@"" forKey:NSKeyValueChangeOldKey];
                    }
                }
                [change setObject:@1 forKey:@"kind"];
                //消息发送给观察者
                [info.observer hp_observeValueForKeyPath:keyPath ofObject:self change:change context:(__bridge void * _Nullable)(info.context)];
            });
        }
    }
}

//获取getter
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

二、kvo函数式编程

上面的自定义自定义kvo与系统的kvo实现都有一个问题,都需要三步曲。代码是分离的可读性并不好。

2.1 注册与回调绑定

可以定义一个block用来处理回调,这样就不需要回调方法了,注册和回调就可以在一起处理了。
直接修改注册方法为block实现:

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

- (void)hp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(HPKVOBlock)block {
……
    //保存观察者信息-数组
    HPKVOBlockInfo *kvoInfo = [[HPKVOBlockInfo alloc] initWitObserver:observer forKeyPath:keyPath handleBlock:block];
……
}
  • block实现也保存在HPKVOBlockInfo中,这样在回调的时候直接执行block实现就可以了。

修改回调逻辑:

NSMutableArray *observerArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHPBlockKVOAssiociateKey));
for (HPKVOBlockInfo *info in observerArray) {//循环调用,可能添加多次。
    if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            info.handleBlock(info.observer, keyPath, oldValue, newValue);
        });
    }
}
  • 在回调的时候直接将新值与旧值一起返回。

注册调用逻辑:

[self.obj hp_addObserver:self forKeyPath:@"name" block:^(id  _Nonnull observer, NSString * _Nonnull keyPath, id  _Nonnull oldValue, id  _Nonnull newValue) {
    NSLog(@"block: oldValue:%@,newValue:%@",oldValue,newValue);
}];

这样就替换了回调函数为block实现了,注册和回调逻辑在一起了。

2.2 kvo自动销毁

上面虽然实现了注册和回调绑定,但是在观察者dealloc的时候仍然需要remove
那么怎么能自动释放不需要主动调用呢?

removeObserver的过程中主要做了两件事,移除关联对象数组中的数据以及指回isa。关联对象不移除的后果是会继续调用回调,那么在调用的时候判断下observer存不存在来处理是否回调就可以了。核心就在指回isa了。

2.2.1 Hook dealloc

首先想到的就是Hook dealloc方法:

+ (void)hp_methodSwizzleWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL isClassMethod:(BOOL)isClassMethod {
    if (!cls) {
        NSLog(@"class is nil");
        return;
    }
    if (!swizzledSEL) {
        NSLog(@"swizzledSEL is nil");
        return;
    }
    //类/元类
    Class swizzleClass = isClassMethod ? object_getClass(cls) : cls;
    Method oriMethod = class_getInstanceMethod(swizzleClass, oriSEL);
    Method swiMethod = class_getInstanceMethod(swizzleClass, swizzledSEL);
    if (!oriMethod) {//原始方法没有实现
        // 在oriMethod为nil时,替换后将swizzledSEL复制一个空实现
        class_addMethod(swizzleClass, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
        //添加一个空的实现
        method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){
           NSLog(@"imp default null implementation");
        }));
    }
    //自己没有则会添加成功,自己有添加失败
    BOOL success = class_addMethod(swizzleClass, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
    if (success) {//自己没有方法添加一个,添加成功则证明自己没有。
       class_replaceMethod(swizzleClass, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    } else { //自己有直接进行交换
       method_exchangeImplementations(oriMethod, swiMethod);
    }
}

+ (void)load {
    [self hp_methodSwizzleWithClass:[self class] oriSEL:NSSelectorFromString(@"dealloc") swizzledSEL:@selector(hp_dealloc) isClassMethod:NO];
}

- (void)hp_dealloc {
   // [self.obj hp_removeObserver:self forKeyPath:@""];
    [self hp_dealloc];
}

hp_dealloc中调用hp_removeObserver移除观察者。这里有个问题是被观察者和keypath从哪里来?这里相当于是观察者的dealloc中调用。所以可以通过在注册的时候对观察者添加关联对象保存被观察者和keyPath

static NSString *const kHPBlockKVOObserverdAssiociateKey = @"HPKVOObserverdAssiociateKey";

@interface HPKVOObservedInfo : NSObject

@property (nonatomic, weak) id observerd;
@property (nonatomic, copy) NSString  *keyPath;

@end

@implementation HPKVOObservedInfo

- (instancetype)initWitObserverd:(NSObject *)observerd forKeyPath:(NSString *)keyPath {
    if (self=[super init]) {
        _observerd = observerd;
        _keyPath  = keyPath;
    }
    return self;
}

@end


- (void)hp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(HPKVOBlock)block {
    ……    
    //保存被观察者信息
    HPKVOObservedInfo *kvoObservedInfo = [[HPKVOObservedInfo alloc] initWitObserverd:self forKeyPath:keyPath];
    NSMutableArray *observerdArray = objc_getAssociatedObject(observer, (__bridge const void * _Nonnull)(kHPBlockKVOObserverdAssiociateKey));
    if (!observerdArray) {
        observerdArray = [NSMutableArray arrayWithCapacity:1];
    }
    [observerdArray addObject:kvoObservedInfo];
    objc_setAssociatedObject(observer, (__bridge const void * _Nonnull)(kHPBlockKVOObserverdAssiociateKey), observerdArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
  • kvoObservedInfo中保存的是self也就是被观察者。
  • 关联对象关联在observer也就是观察者身上。

这个时候在dealloc中遍历对其进行移除:

- (void)hp_dealloc {
    NSMutableArray *observerdArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHPBlockKVOObserverdAssiociateKey));
    for (HPKVOObservedInfo *info in observerdArray) {
        if (info.observerd) {
            [info.observerd hp_removeObserver:self forKeyPath:info.keyPath];
        }
    }
    [self hp_dealloc];
}

当然这里的方法执行只针对被观察者没有释放的情况,释放了observerd就不存在了不需要调用remove逻辑了。

2.2.2 优化Hook逻辑

上面在+ loadHook dealloc方法是在NSObject分类中处理的,那么意味着所有的类的dealloc方法都被Hook了。显然这么做是不合理的。
逻辑就是仅对需要的类进行Hook dealloc方法,所以将Hook延迟到addObserver中:

- (void)hp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(HPKVOBlock)block {
  ……
 //hook dealloc
 [[observer class] hp_methodSwizzleWithClass:[observer class] oriSEL:NSSelectorFromString(@"dealloc") swizzledSEL:@selector(hp_dealloc) isClassMethod:NO];
}

但是只应该对dealloc hook一次,否则又交换回来了。要么做标记,要么在创建kvo子类的时候进行hook。显然在创建子类的时候更合适。修改逻辑如下:

//申请类-注册类-添加方法
- (Class)_creatKVOClassWithKeyPath:(NSString *)keyPath observer:(NSObject *)observer {
    //这里重写class后kvo子类也返回的是父类的名字
    NSString *superClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",kHPBlockKVOClassPrefix,superClassName];
    Class newClass = NSClassFromString(newClassName);
    //类是否存在
    if (!newClass)  {//不存在需要创建类
        //1:申请类 父类、新类名称、额外空间
        newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
        //2:注册类
        objc_registerClassPair(newClass);
        //3:添加class方法,class返回父类信息 这里是`-class`
        SEL classSEL = NSSelectorFromString(@"class");
        Method classMethod = class_getInstanceMethod([self class], classSEL);
        const char *classTypes = method_getTypeEncoding(classMethod);
        class_addMethod(newClass, classSEL, (IMP)_hp_class, classTypes);
        
        //hook dealloc
        [[observer class] hp_methodSwizzleWithClass:[observer class] oriSEL:NSSelectorFromString(@"dealloc") swizzledSEL:@selector(hp_dealloc) isClassMethod:NO];
    }
    //4:添加setter方法
    SEL setterSEL = NSSelectorFromString(_setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod([self class], setterSEL);
    const char *setterTypes = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)_hp_setter, setterTypes);
    
    return newClass;
}

完整实现代码:

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

@interface NSObject (HP_KVO_Block)

- (void)hp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(HPKVOBlock)block;

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

@end

#import "NSObject+HP_KVO_Block.h"
#import 
#import 
#import "HPKVOInfo.h"

static NSString *const kHPBlockKVOClassPrefix = @"HPKVONotifying_";
static NSString *const kHPBlockKVOAssiociateKey = @"HPKVOAssiociateKey";

static NSString *const kHPBlockKVOObserverdAssiociateKey = @"HPKVOObserverdAssiociateKey";

@interface HPKVOBlockInfo : NSObject

@property (nonatomic, weak) id observer;
@property (nonatomic, copy) NSString  *keyPath;
@property (nonatomic, copy) HPKVOBlock  handleBlock;

@end

@implementation HPKVOBlockInfo

- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(HPKVOBlock)block {
    if (self=[super init]) {
        _observer = observer;
        _keyPath  = keyPath;
        _handleBlock = block;
    }
    return self;
}

@end

@interface HPKVOObservedInfo : NSObject

@property (nonatomic, weak) id observerd;
@property (nonatomic, copy) NSString  *keyPath;

@end

@implementation HPKVOObservedInfo

- (instancetype)initWitObserverd:(NSObject *)observerd forKeyPath:(NSString *)keyPath {
    if (self=[super init]) {
        _observerd = observerd;
        _keyPath  = keyPath;
    }
    return self;
}

@end

@implementation NSObject (HP_KVO_Block)


- (void)hp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(HPKVOBlock)block {
    //1.参数判断 以及 setter检查
    if (!observer || !keyPath) return;
    BOOL result = [self _handleSetterMethodFromKeyPath:keyPath];
    if (!result) return;
    
    //2.isa_swizzle 申请类-注册类-添加方法
    Class newClass = [self _creatKVOClassWithKeyPath:keyPath observer:observer];
    
    //3.isa 指向子类
    object_setClass(self, newClass);
    //4.setter逻辑处理
    //保存观察者信息-数组
    HPKVOBlockInfo *kvoInfo = [[HPKVOBlockInfo alloc] initWitObserver:observer forKeyPath:keyPath handleBlock:block];
    NSMutableArray *observerArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHPBlockKVOAssiociateKey));
    if (!observerArray) {
        observerArray = [NSMutableArray arrayWithCapacity:1];
    }
    [observerArray addObject:kvoInfo];
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kHPBlockKVOAssiociateKey), observerArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    //保存被观察者信息
    HPKVOObservedInfo *kvoObservedInfo = [[HPKVOObservedInfo alloc] initWitObserverd:self forKeyPath:keyPath];
    NSMutableArray *observerdArray = objc_getAssociatedObject(observer, (__bridge const void * _Nonnull)(kHPBlockKVOObserverdAssiociateKey));
    if (!observerdArray) {
        observerdArray = [NSMutableArray arrayWithCapacity:1];
    }
    [observerdArray addObject:kvoObservedInfo];
    objc_setAssociatedObject(observer, (__bridge const void * _Nonnull)(kHPBlockKVOObserverdAssiociateKey), observerdArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)hp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
    NSMutableArray *observerArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHPBlockKVOAssiociateKey));
    if (observerArray.count <= 0) {
        return;
    }
    
    NSMutableArray *tempArray = [observerArray mutableCopy];
    for (HPKVOInfo *info in tempArray) {
        if ([info.keyPath isEqualToString:keyPath]) {
            if (info.observer) {
                if (info.observer == observer) {
                    [observerArray removeObject:info];
                }
            } else {
                [observerArray removeObject:info];
            }
        }
    }
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kHPBlockKVOAssiociateKey), observerArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //已经全部移除了
    if (observerArray.count <= 0) {
        //isa指回给父类
        Class superClass = [self class];
        object_setClass(self, superClass);
    }
}

- (BOOL)_handleSetterMethodFromKeyPath:(NSString *)keyPath {
    SEL setterSeletor = NSSelectorFromString(_setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod(object_getClass(self), setterSeletor);
    NSAssert(setterMethod, @"%@ setter is not exist",keyPath);
    return setterMethod ? YES : NO;
}

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

//申请类-注册类-添加方法
- (Class)_creatKVOClassWithKeyPath:(NSString *)keyPath observer:(NSObject *)observer {
    //这里重写class后kvo子类也返回的是父类的名字
    NSString *superClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",kHPBlockKVOClassPrefix,superClassName];
    Class newClass = NSClassFromString(newClassName);
    //类是否存在
    if (!newClass)  {//不存在需要创建类
        //1:申请类 父类、新类名称、额外空间
        newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
        //2:注册类
        objc_registerClassPair(newClass);
        //3:添加class方法,class返回父类信息 这里是`-class`
        SEL classSEL = NSSelectorFromString(@"class");
        Method classMethod = class_getInstanceMethod([self class], classSEL);
        const char *classTypes = method_getTypeEncoding(classMethod);
        class_addMethod(newClass, classSEL, (IMP)_hp_class, classTypes);
        
        //hook dealloc
        [[observer class] hp_methodSwizzleWithClass:[observer class] oriSEL:NSSelectorFromString(@"dealloc") swizzledSEL:@selector(hp_dealloc) isClassMethod:NO];
    }
    //4:添加setter方法
    SEL setterSEL = NSSelectorFromString(_setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod([self class], setterSEL);
    const char *setterTypes = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)_hp_setter, setterTypes);
    
    return newClass;
}

//返回父类信息
Class _hp_class(id self,SEL _cmd) {
    return class_getSuperclass(object_getClass(self));
}

static void _hp_setter(id self,SEL _cmd,id newValue) {
    //自动开关判断,省略
    //保存旧值
    NSString *keyPath = _getterForSetter(NSStringFromSelector(_cmd));
    id oldValue = [self valueForKey:keyPath];
    //1.调用父类的setter(也可以通过performSelector调用)
    void (*hp_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
    struct objc_super super_struct = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self)),
    };
    hp_msgSendSuper(&super_struct,_cmd,newValue);
    
    //2.通知观察者
    NSMutableArray *observerArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHPBlockKVOAssiociateKey));
    for (HPKVOBlockInfo *info in observerArray) {//循环调用,可能添加多次。
        if ([info.keyPath isEqualToString:keyPath] && info.handleBlock && info.observer) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                info.handleBlock(info.observer, keyPath, oldValue, newValue);
            });
        }
    }
}

//获取getter
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];
}

+ (void)hp_methodSwizzleWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL isClassMethod:(BOOL)isClassMethod {
    if (!cls) {
        NSLog(@"class is nil");
        return;
    }
    if (!swizzledSEL) {
        NSLog(@"swizzledSEL is nil");
        return;
    }
    //类/元类
    Class swizzleClass = isClassMethod ? object_getClass(cls) : cls;
    Method oriMethod = class_getInstanceMethod(swizzleClass, oriSEL);
    Method swiMethod = class_getInstanceMethod(swizzleClass, swizzledSEL);
    if (!oriMethod) {//原始方法没有实现
        // 在oriMethod为nil时,替换后将swizzledSEL复制一个空实现
        class_addMethod(swizzleClass, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
        //添加一个空的实现
        method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){
           NSLog(@"imp default null implementation");
        }));
    }
    //自己没有则会添加成功,自己有添加失败
    BOOL success = class_addMethod(swizzleClass, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
    if (success) {//自己没有方法添加一个,添加成功则证明自己没有。
       class_replaceMethod(swizzleClass, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    } else { //自己有直接进行交换
       method_exchangeImplementations(oriMethod, swiMethod);
    }
}

- (void)hp_dealloc {
    NSMutableArray *observerdArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHPBlockKVOObserverdAssiociateKey));
    for (HPKVOObservedInfo *info in observerdArray) {
        if (info.observerd) {
            [info.observerd hp_removeObserver:self forKeyPath:info.keyPath];
        }
    }
    [self hp_dealloc];
}

@end

三、系统kvo容错处理

在上面自定义kvo中处理了自动移除观察者逻辑,以及将回调使用block实现。在实际使用系统kvo的时候有以下问题:
1.多次添加同一观察者会进行多次回调。
2.某个属性没有被观察,在dealloc中移除会造成crash
3.多次移除观察者也会造成crash
4.不移除观察者有可能造成crash。(观察者释放后被观察者调用回调)

那么要避免就要在添加和移除以及dealloc过程中做容错处理。

NSObject(NSKeyValueObservingCustomization)中发现了observationInfo

/*
Take or return a pointer that identifies information about all of the observers that are registered with the receiver, the options that were used at registration-time, etc. 
The default implementation of these methods store observation info in a global dictionary keyed by the receivers' pointers. For improved performance, you can override these methods to store the opaque data pointer in an instance variable. 
Overrides of these methods must not attempt to send Objective-C messages to the passed-in observation info, including -retain and -release.
*/
@property (nullable) void *observationInfo NS_RETURNS_INNER_POINTER;

根据注释可以看到默认情况下observationInfo中保存了所有观察者信息。
那么observationInfo保存在哪里呢?直接代码验证下:

NSLog(@"observed before %@",self.obj.observationInfo);
NSLog(@"observe before %@",self.observationInfo);
[self.obj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
NSLog(@"observe after %@",self.observationInfo);
NSLog(@"observed after %@",self.obj.observationInfo);

输出:

observed before (null)
observe before (null)
observe after (null)
observed after  (
 Context: 0x0, Property: 0x600001ee1050>
)

可以看到在注册后存入到了被观察者中,类型是NSKeyValueObservationInfo,它是一个私有类。NSKeyValueObservationInfo中保存的是NSKeyValueObservance
NSKeyValueObservationInfo保存了NSKeyValueObservance集合,NSKeyValueObservance中保存了观察者注册的时候的信息。
既然是在Foundation框架中,那么dump一下这个动态库的头文件(越狱手机使用classdump-dyld导出头文件)。

NSKeyValueObservationInfo头文件:

@class NSArray;
@interface NSKeyValueObservationInfo : NSObject {
    NSArray* _observances;
    unsigned long long _cachedHash;
    BOOL _cachedIsShareable;
}
@property (nonatomic,readonly) BOOL containsOnlyInternalObservationHelpers; 
-(void)dealloc;
-(unsigned long long)hash;
-(id)_initWithObservances:(id*)arg1 count:(unsigned long long)arg2 hashValue:(unsigned long long)arg3 ;
-(id)description;
-(BOOL)containsOnlyInternalObservationHelpers;
-(BOOL)isEqual:(id)arg1 ;
-(id)_copyByAddingObservance:(id)arg1 ;
@end

NSKeyValueObservance头文件:

@class NSObject, NSKeyValueProperty;
@interface NSKeyValueObservance : NSObject {
    NSObject* _observer;
    NSKeyValueProperty* _property;
    void* _context;
    NSObject* _originalObservable;
    unsigned _options : 6;
    unsigned _cachedIsShareable : 1;
    unsigned _isInternalObservationHelper : 1;
}
-(id)_initWithObserver:(id)arg1 property:(id)arg2 options:(unsigned long long)arg3 context:(void*)arg4 originalObservable:(id)arg5 ;
-(unsigned long long)hash;
-(id)description;
-(BOOL)isEqual:(id)arg1 ;
-(void)observeValueForKeyPath:(id)arg1 ofObject:(id)arg2 change:(id)arg3 context:(void*)arg4 ;
@end

那么基本可以确定_observances中保存的是NSKeyValueObservance
代码验证:

image.png

最终在NSKeyValueProperty中找到了_keyPath:

image.png

它的定义如下:

@class NSKeyValueContainerClass, NSString;
@interface NSKeyValueProperty : NSObject  {
    NSKeyValueContainerClass* _containerClass;
    NSString* _keyPath;
}
-(Class)isaForAutonotifying;
-(id)_initWithContainerClass:(id)arg1 keyPath:(id)arg2 propertiesBeingInitialized:(CFSetRef)arg3 ;
-(id)dependentValueKeyOrKeysIsASet:(BOOL*)arg1 ;
-(void)object:(id)arg1 withObservance:(id)arg2 didChangeValueForKeyOrKeys:(id)arg3 recurse:(BOOL)arg4 forwardingValues:(SCD_Struct_NS48)arg5 ;
-(BOOL)object:(id)arg1 withObservance:(id)arg2 willChangeValueForKeyOrKeys:(id)arg3 recurse:(BOOL)arg4 forwardingValues:(SCD_Struct_NS48*)arg5 ;
-(void)object:(id)arg1 didAddObservance:(id)arg2 recurse:(BOOL)arg3 ;
-(id)restOfKeyPathIfContainedByValueForKeyPath:(id)arg1 ;
-(void)object:(id)arg1 didRemoveObservance:(id)arg2 recurse:(BOOL)arg3 ;
-(BOOL)matchesWithoutOperatorComponentsKeyPath:(id)arg1 ;
-(id)copyWithZone:(NSZone*)arg1 ;
-(id)keyPath;
-(void)dealloc;
-(id)keyPathIfAffectedByValueForKey:(id)arg1 exactMatch:(BOOL*)arg2 ;
-(id)keyPathIfAffectedByValueForMemberOfKeys:(id)arg1 ;
@end
  • _observer存储在NSKeyValueObservance 中。
  • _keyPath存储在NSKeyValueObservance_propertyNSKeyValueProperty)中。

3.1Hook注册和移除方法

要对系统方法进行容错处理那么最好的办法就是Hook了,直接对添加和移除的3个方法进行Hook处理:

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self hp_methodSwizzleWithClass:self oriSEL:@selector(addObserver:forKeyPath:options:context:) swizzledSEL:@selector(hp_addObserver:forKeyPath:options:context:) isClassMethod:NO];
        [self hp_methodSwizzleWithClass:self oriSEL:@selector(removeObserver:forKeyPath:context:) swizzledSEL:@selector(hp_removeObserver:forKeyPath:context:)isClassMethod:NO];
        [self hp_methodSwizzleWithClass:self oriSEL:@selector(removeObserver:forKeyPath:) swizzledSEL:@selector(hp_removeObserver:forKeyPath:)isClassMethod:NO];
    });
}
  • 由于removeObserver:forKeyPath:底层调用的不是removeObserver:forKeyPath:context:所以两个方法都要Hook

那么核心逻辑就是怎么判断observer对应的keyPath是否存在。由于observationInfo存储的是私有类,那么直接通过kvc获取值:

- (BOOL)keyPathIsExist:(NSString *)sarchKeyPath observer:(id)observer {
    BOOL findKey = NO;
    id info = self.observationInfo;
    if (info) {
        NSArray *observances = [info valueForKeyPath:@"_observances"];
        for (id observance in observances) {
            id tempObserver = [observance valueForKey:@"_observer"];
            if (tempObserver == observer) {
                NSString *keyPath = [observance valueForKeyPath:@"_property._keyPath"];
                if ([keyPath isEqualToString:sarchKeyPath]) {
                    findKey = YES;
                    break;
                }
            }
        }
    }
    return findKey;
}

Hook的具体实现:

- (void)hp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
    if ([self keyPathIsExist:keyPath observer:observer]) {//observer 观察者已经添加了对应key的观察,再次添加不做处理。
        return;
    }
    [self hp_addObserver:observer forKeyPath:keyPath options:options context:context];
}

- (void)hp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context {
    if ([self keyPathIsExist:keyPath observer:observer]) {//key存在才移除
        [self hp_removeObserver:observer forKeyPath:keyPath context:context];
    }
}

- (void)hp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
    if ([self keyPathIsExist:keyPath observer:observer]) {//key存在才移除
        [self hp_removeObserver:observer forKeyPath:keyPath];
    }
}
  • 这样就解决了重复添加和移除的问题。

3.2 自动移除观察者

3.1中解决了重复添加和移除的问题,还有一个问题是dealloc的时候自动移除。这块思路与自定义kvo相同,通过Hook观察者的的dealloc实现。

- (void)hp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
    if ([self keyPathIsExist:keyPath observer:observer]) {//observer 观察者已经添加了对应key的观察,再次添加不做处理。
        return;
    }
    NSString *className = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"NSKVONotifying_%@",className];
    Class newClass = NSClassFromString(newClassName);
    if (!newClass) {//类不存在的时候进行 hook 观察者 dealloc
        //hook dealloc
        [[observer class] hp_methodSwizzleWithClass:[observer class] oriSEL:NSSelectorFromString(@"dealloc") swizzledSEL:@selector(hp_dealloc) isClassMethod:NO];
    }
    [self hp_addObserver:observer forKeyPath:keyPath options:options context:context];
}

- (void)hp_dealloc {
    [self hp_removeSelfAllObserverd];
    [self hp_dealloc];
}
  • kvo子类已经存在的时候证明已经hook过了。

deallocself.observationInfo是获取不到信息的,因为observationInfo是存储在被观察者中的。所以还需要自己存储信息。
修改如下:

static NSString *const kHPSafeKVOObserverdAssiociateKey = @"HPSafeKVOObserverdAssiociateKey";

@interface HPSafeKVOObservedInfo : NSObject

@property (nonatomic, weak) id observerd;
@property (nonatomic, copy) NSString  *keyPath;
@property (nonatomic, strong) id context;

@end

@implementation HPSafeKVOObservedInfo

- (instancetype)initWitObserverd:(NSObject *)observerd forKeyPath:(NSString *)keyPath context:(nullable void *)context {
    if (self=[super init]) {
        _observerd = observerd;
        _keyPath = keyPath;
        _context = (__bridge id)(context);
    }
    return self;
}

@end

- (void)hp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
    if ([self keyPathIsExist:keyPath observer:observer]) {//observer 观察者已经添加了对应key的观察,再次添加不做处理。
        return;
    }
    NSString *className = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"NSKVONotifying_%@",className];
    Class newClass = NSClassFromString(newClassName);
    if (!newClass) {//类不存在的时候进行 hook 观察者 dealloc
        //hook dealloc
        [[observer class] hp_methodSwizzleWithClass:[observer class] oriSEL:NSSelectorFromString(@"dealloc") swizzledSEL:@selector(hp_dealloc) isClassMethod:NO];
    }
    
    //保存被观察者信息
    HPSafeKVOObservedInfo *kvoObservedInfo = [[HPSafeKVOObservedInfo alloc] initWitObserverd:self forKeyPath:keyPath context:context];
    NSMutableArray *observerdArray = objc_getAssociatedObject(observer, (__bridge const void * _Nonnull)(kHPSafeKVOObserverdAssiociateKey));
    if (!observerdArray) {
        observerdArray = [NSMutableArray arrayWithCapacity:1];
    }
    [observerdArray addObject:kvoObservedInfo];
    objc_setAssociatedObject(observer, (__bridge const void * _Nonnull)(kHPSafeKVOObserverdAssiociateKey), observerdArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    //调用原始方法
    [self hp_addObserver:observer forKeyPath:keyPath options:options context:context];
}

hp_dealloc中主动调用移除方法:

- (void)hp_dealloc {
    [self hp_removeSelfAllObserverd];
    [self hp_dealloc];
}

- (void)hp_removeSelfAllObserverd {
    NSMutableArray *observerdArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHPSafeKVOObserverdAssiociateKey));
    for (HPSafeKVOObservedInfo *info in observerdArray) {
        if (info.observerd) {
            //调用系统方法,已经hook了,走hook逻辑。
            if (info.context) {
                [info.observerd removeObserver:self forKeyPath:info.keyPath context:(__bridge void * _Nullable)(info.context)];
            } else {
                [info.observerd removeObserver:self forKeyPath:info.keyPath];
            }
        }
    }
}

这样在dealloc的时候就主动清空了已经释放掉的observer

3.3 问题处理

上面这样处理后在退出页面的时候发生了crash(非必现),堆栈如下:

image.png

原因是没有实现_invalidate方法。是在[UIScreen dealloc]中进行调用的。断点发现:
image.png

UIScreen 观察了 CADisplaycloned。但是在释放的过程中UIScreen却没有调用到Hookhp_dealloc中,对应的汇编实现:

int -[UIScreen dealloc](int arg0) {
    [r0 _invalidate];
    __UIScreenWriteDisplayConfiguration(r0, 0x0, 0x0);
    r0 = [[&stack[0] super] dealloc];
    return r0;
}

int -[UIScreen _invalidate](int arg0) {
    var_10 = r20;
    stack[-24] = r19;
    r31 = r31 + 0xffffffffffffffe0;
    saved_fp = r29;
    stack[-8] = r30;
    r19 = arg0;
    *(int16_t *)(arg0 + 0xb0) = *(int16_t *)(arg0 + 0xb0) & 0xffffffffffffffcf;
    r0 = [NSNotificationCenter defaultCenter];
    r0 = [r0 retain];
    [r0 removeObserver:r19];
    [r0 release];
    if ([r19 _isCarScreen] == 0x0) goto loc_7495b4;

loc_749570:
    r0 = __UIInternalPreferenceUsesDefault_751e78(0x19080b0, @"ApplySceneUserInterfaceStyleToCarScreen", 0xec7178);
    if (((*(int8_t *)0x19080b4 & 0x1) == 0x0) || (r0 != 0x0)) goto loc_74959c;

loc_7495b4:
    [r19 _endObservingBacklightLevelNotifications];
    [r19 _setSoftwareDimmingWindow:0x0];
    r0 = *(r19 + 0x90);
    r0 = [r0 _setScreen:0x0];
    return r0;

loc_74959c:
    CFNotificationCenterRemoveObserver(CFNotificationCenterGetDarwinNotifyCenter(), r19, @"CarPlayUserInterfaceStyleDidChangeNotification", 0x0);
    goto loc_7495b4;
}

那么意味着是否没有替换成功?
_UIScreenWriteDisplayConfiguration中确实先移除后添加:

image.png

是否进行注册是通过rax控制的。也就是__UIScreenIsCapturedValueOverride.isCapturedValue控制的。
经过测试只要在系统自动调用UIScreen initialize之前调用一个UIScreen相关方法就不走kvo设置逻辑了,比如:

[UIScreen mainScreen]
//[UIScreen class]

目前不清楚原因。所以处理这个问题有两个思路:

  1. + load进行方法交换的时候先调用[UIScreen class]
    image.png
  2. 在注册的时候对系统类或者自己的类进行过滤。
  • 2.1只排除UIScreen
if ([observer isKindOfClass:[UIScreen class]]) {
    [self hp_addObserver:observer forKeyPath:keyPath options:options context:context];
    return;
}
  • 2.2排除系统类
NSString *className = NSStringFromClass([observer class]);
if ([className hasPrefix:@"NS"] || [className hasPrefix:@"UI"]) { //排除某些系统类。
    [self hp_addObserver:observer forKeyPath:keyPath options:options context:context];
    return;
}
  • 2.3 只处理自己的类
NSString *className = NSStringFromClass([observer class]);
if (![className hasPrefix:@"HP"]) { //排除某些系统类。
    [self hp_addObserver:observer forKeyPath:keyPath options:options context:context];
    return;
}

四、KVOController

上面Hook系统kvo相关方法的方式侵入太严重了,我们要做的其实只是需要对自己的调用负责而已,可以通过中间类来完成。这块有很多第三方框架,其中Facebook提供的KVOController是很优秀的一个框架。在这篇文章中将对这个库进行简单分析。

4.1 KVOController 的使用

#import 


- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.KVOController = [FBKVOController controllerWithObserver:self];
    [self.KVOController observe:self.obj keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary * _Nonnull change) {
        NSLog(@"change:%@",change);
    }];
    
    [self.KVOController observe:self.obj keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary * _Nonnull change) {
        NSLog(@"change:%@",change);
    }];

    [self.KVOController observe:self.obj keyPath:@"nickName" options:NSKeyValueObservingOptionNew action:@selector(hp_NickNameChange:object:)];
}

- (void)hp_NickNameChange:(NSDictionary *)change object:(id)object {
    NSLog(@"change:%@ object:%@",change,object);
}

输出:

change:{
    FBKVONotificationKeyPathKey = name;
    kind = 1;
    new = HP111;
}
change:{
    kind = 1;
    new = cat111;
} object:
  • vc持有FBKVOController实例KVOController。在NSObject+FBKVOController.h的关联属性。
  • 通过FBKVOController实例进行注册。注册方式提供了多种。
  • 对于重复添加会进行判断直接返回。
  • 会自动进行移除操作。

4.2 KVOController 实现分析

KVOController主要是使用了中介者模式,官方kvo使用麻烦的点在于使用需要三部曲。KVOController核心就是将三部曲进行了底层封装,上层只需要关心业务逻辑。

FBKVOController会进行注册、移除以及回调的处理(回调包括blockaction以及兼容系统的observe回调)。是对外暴露的交互类。使用FBKVOController分为两步:

  1. 使用 controllerWithObserver 初始化FBKVOController实例。
  2. 使用observe:进行注册。

4.2.1 FBKVOController 初始化

controllerWithObserver
controllerWithObserver最终会调用到initWithObserver中:

- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved
{
  self = [super init];
  if (nil != self) {
    _observer = observer;
    NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
    _objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
    pthread_mutex_init(&_lock, NULL);
  }
  return self;
}
  • _observer是观察者,FBKVOController的属性。
@property (nullable, nonatomic, weak, readonly) id observer;

weak类型,因为FBKVOController本身被观察者持有了。

  • _objectInfosMap根据retainObserved进行NSMapTable内存管理初始化配置,FBKVOController的成员变量。其中保存的是一个被观察者对应多个_FBKVOInfo(也就是被观察对象对应多个keyPath):
  NSMapTable *> *_objectInfosMap;

这里_FBKVOInfo是放在NSMutableSet中的,说明是去重的。

4.2.2 FBKVOController 注册

由于各个observe方式的原理差不多,这里只分析block的形式。

- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block
{
  NSAssert(0 != keyPath.length && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block);
  if (nil == object || 0 == keyPath.length || NULL == block) {
    return;
  }

  // create info
  _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];

  // observe object with info
  [self _observe:object info:info];
}
  • 首先一些条件容错判断。
  • 构造_FBKVOInfo。保存FBKVOControllerkeyPathoptions以及block
  • 调用_observe:(id)object info:(_FBKVOInfo *)info

4.2.2.1 _FBKVOInfo

@implementation _FBKVOInfo
{
@public
  __weak FBKVOController *_controller;
  NSString *_keyPath;
  NSKeyValueObservingOptions _options;
  SEL _action;
  void *_context;
  FBKVONotificationBlock _block;
  _FBKVOInfoState _state;
}
  • _FBKVOInfo中保存了相关数据信息。

并且重写了isEqualhash方法:

- (NSUInteger)hash
{
  return [_keyPath hash];
}

- (BOOL)isEqual:(id)object
{
  if (nil == object) {
    return NO;
  }
  if (self == object) {
    return YES;
  }
  if (![object isKindOfClass:[self class]]) {
    return NO;
  }
  return [_keyPath isEqualToString:((_FBKVOInfo *)object)->_keyPath];
}

说明只要_keyPath相同就认为是同一对象。

4.2.2.2 _observe: info:

- (void)_observe:(id)object info:(_FBKVOInfo *)info
{
  // lock
  pthread_mutex_lock(&_lock);

  //从TableMap中获取 object(被观察者) 对应的 set
  NSMutableSet *infos = [_objectInfosMap objectForKey:object];

  // check for info existence
  //判断对应的keypath info 是否存在
  _FBKVOInfo *existingInfo = [infos member:info];
  if (nil != existingInfo) {
    //存在直接返回,这里就相当于对于同一个观察者排除了相同的keypath
    // observation info already exists; do not observe it again

    // unlock and return
    pthread_mutex_unlock(&_lock);
    return;
  }

  // lazilly create set of infos
  //TableMap数据为空进行创建设置
  if (nil == infos) {
    infos = [NSMutableSet set];
    //<被观察者 - keypaths info>
    [_objectInfosMap setObject:infos forKey:object];
  }

  // add info and oberve
  //keypaths info添加 keypath info
  [infos addObject:info];

  // unlock prior to callout
  pthread_mutex_unlock(&_lock);
  //注册
  [[_FBKVOSharedController sharedController] observe:object info:info];
}
  • 首先判断kayPath是否已经被注册了,注册了直接返回,这里也就进行了去重处理。
  • 将构造的_FBKVOInfo信息添加进_objectInfosMap中。
  • 调用_FBKVOSharedController进行真正的注册。

member:说明
member会调用到_FBKVOInfo中的hash以及isEqual进行判断对象是否存在,也就是判断keyPath对应的对象是否存在。

image.png

汇编中也能找到相关的调用:
image.png

汇编伪代码:
image.png

官方API说明:
member:

isEqual:

源码实现:

+ (NSUInteger)hash {
    return _objc_rootHash(self);
}

- (NSUInteger)hash {
    return _objc_rootHash(self);
}

+ (BOOL)isEqual:(id)obj {
    return obj == (id)self;
}

- (BOOL)isEqual:(id)obj {
    return obj == self;
}

uintptr_t
_objc_rootHash(id obj)
{
    return (uintptr_t)obj;
}
  • hash默认实现将对象地址转换为uintptr_t类型返回。
  • isEqual:直接判断地址是否相同。
  • member:根据汇编可以看到大概逻辑是先计算参数的hash,然后集合中的元素调用isEqual参数是hash值。

4.2.2.3 _unobserve:info:

- (void)_unobserve:(id)object info:(_FBKVOInfo *)info
{
  // lock
  pthread_mutex_lock(&_lock);

  // get observation infos
  NSMutableSet *infos = [_objectInfosMap objectForKey:object];

  // lookup registered info instance
  _FBKVOInfo *registeredInfo = [infos member:info];

  if (nil != registeredInfo) {
    [infos removeObject:registeredInfo];

    // remove no longer used infos
    if (0 == infos.count) {
      [_objectInfosMap removeObjectForKey:object];
    }
  }

  // unlock
  pthread_mutex_unlock(&_lock);

  // unobserve
  [[_FBKVOSharedController sharedController] unobserve:object info:registeredInfo];
}

- (void)_unobserve:(id)object
{
  // lock
  pthread_mutex_lock(&_lock);

  NSMutableSet *infos = [_objectInfosMap objectForKey:object];

  // remove infos
  [_objectInfosMap removeObjectForKey:object];

  // unlock
  pthread_mutex_unlock(&_lock);

  // unobserve
  [[_FBKVOSharedController sharedController] unobserve:object infos:infos];
}

- (void)_unobserveAll
{
  // lock
  pthread_mutex_lock(&_lock);

  NSMapTable *objectInfoMaps = [_objectInfosMap copy];

  // clear table and map
  [_objectInfosMap removeAllObjects];

  // unlock
  pthread_mutex_unlock(&_lock);

  _FBKVOSharedController *shareController = [_FBKVOSharedController sharedController];

  for (id object in objectInfoMaps) {
    // unobserve each registered object and infos
    NSSet *infos = [objectInfoMaps objectForKey:object];
    [shareController unobserve:object infos:infos];
  }
}
  • _unobserve提供了3个方法进行移除。分别对应keyPathobserverd(被观察对象)、observer(观察者)。
  • 最终都是通过_FBKVOSharedControllerunobserve进行移除。

4.2.3 _FBKVOSharedController

[[_FBKVOSharedController sharedController] observe:object info:info];

4.2.3.1 sharedController

_FBKVOSharedController是个单例,有成员变量_infos:

 NSHashTable<_FBKVOInfo *> *_infos;

不设计FBKVOController为单例是因为它被观察者持有,它是单例观察者就无法释放了。这里_infos存储的是所有类的_FBKVOInfo信息。

- (instancetype)init
{
  self = [super init];
  if (nil != self) {
    NSHashTable *infos = [NSHashTable alloc];
#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED
    _infos = [infos initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
    if ([NSHashTable respondsToSelector:@selector(weakObjectsHashTable)]) {
      _infos = [infos initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
    } else {
      // silence deprecated warnings
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
      _infos = [infos initWithOptions:NSPointerFunctionsZeroingWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
#pragma clang diagnostic pop
    }

#endif
    pthread_mutex_init(&_mutex, NULL);
  }
  return self;
}
  • infos的初始化是weak的,也就是它不影响_FBKVOInfo的引用计数。

4.2.3.2 observe: info:

- (void)observe:(id)object info:(nullable _FBKVOInfo *)info
{
  if (nil == info) {
    return;
  }

  // register info
  pthread_mutex_lock(&_mutex);
  [_infos addObject:info];
  pthread_mutex_unlock(&_mutex);

  // add observer
  //被观察者调用官方kvo进行注册,context 传递的是 _FBKVOInfo 信息。
  [object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];

  if (info->_state == _FBKVOInfoStateInitial) {
    //状态变为Observing
    info->_state = _FBKVOInfoStateObserving;
  } else if (info->_state == _FBKVOInfoStateNotObserving) {
    // this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions,
    // and the observer is unregistered within the callback block.
    // at this time the object has been registered as an observer (in Foundation KVO),
    // so we can safely unobserve it.
    //当状态变为不在观察时移除
    [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
  }
}
  • 首先自己持有了传进来的info信息。
  • observe: info:中调用系统kvo方法观察注册。context传递的是_FBKVOInfo信息。
  • 对于系统而言观察者是_FBKVOSharedController

4.2.3.3 unobserve: info:

- (void)unobserve:(id)object info:(nullable _FBKVOInfo *)info
{
  if (nil == info) {
    return;
  }

  // unregister info
  pthread_mutex_lock(&_mutex);
  [_infos removeObject:info];
  pthread_mutex_unlock(&_mutex);

  // remove observer
  if (info->_state == _FBKVOInfoStateObserving) {
    [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
  }
  info->_state = _FBKVOInfoStateNotObserving;
}
  • 调用系统的removeObserver移除观察。

4.2.3.4 observeValueForKeyPath

既然是在4.2.3_FBKVOSharedController中进行的注册,那么系统的回调observeValueForKeyPath必然由它实现:

- (void)observeValueForKeyPath:(nullable NSString *)keyPath
                      ofObject:(nullable id)object
                        change:(nullable NSDictionary *)change
                       context:(nullable void *)context
{
  NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change);

  _FBKVOInfo *info;

  {
    // lookup context in registered infos, taking out a strong reference only if it exists
    pthread_mutex_lock(&_mutex);
    info = [_infos member:(__bridge id)context];
    pthread_mutex_unlock(&_mutex);
  }

  if (nil != info) {

    // take strong reference to controller
    FBKVOController *controller = info->_controller;
    if (nil != controller) {

      // take strong reference to observer
      //观察者
      id observer = controller.observer;
      if (nil != observer) {

        // dispatch custom block or action, fall back to default action
        if (info->_block) {
          NSDictionary *changeWithKeyPath = change;
          // add the keyPath to the change dictionary for clarity when mulitple keyPaths are being observed
          if (keyPath) {
            //将keypath加入字典中
            NSMutableDictionary *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey];
            [mChange addEntriesFromDictionary:change];
            changeWithKeyPath = [mChange copy];
          }
          info->_block(observer, object, changeWithKeyPath);
        } else if (info->_action) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
          [observer performSelector:info->_action withObject:change withObject:object];
#pragma clang diagnostic pop
        } else {
          [observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
        }
      }
    }
  }
}
  • info中获取观察者,info信息是context传递过来的。
  • _FBKVOInfo存在的情况下根据类型(blockaction、系统原始回调)进行了回调。block回调的过程中添加了keyPath

4.2.4 自动移除观察者

FBKVOControllerdealloc中调用了unobserveAll进行移除:

- (void)dealloc
{
  [self unobserveAll];
  pthread_mutex_destroy(&_lock);
}

由于FBKVOController的实例是被观察者持有的,所以当观察者dealloc的时候FBKVOController实例也就dealloc了。在这里调用就相当于在观察者dealloc中调用了移除。

FBKVOController流程

FBKVOController流程

五、通过gnustep探索

kvokvc相关的代码苹果并没有开源,对于它们的探索可以通过gnustep查看原理,gnustep中有一些苹果早期底层的实现。

image.png

5.1 addObserver

image.png
  • setup()中是对一些表的初始化。
  • replacementForClass创建并注册kvo类。
  • 创建GSKVOInfo信息加入Map中。然后进行isa替换。
  • 重写setter方法。

5.1.1 setup()

image.png

其中设置了baseClass就是GSKVOBase

5.1.2 replacementForClass

image.png

创建类并加入classTable中。

initWithClass

image.png

  • 拼接名字创建类。
  • baseClass就是GSKVOBase,这里是将GSKVOBase类的相关信息同步到创建的类中。

GSObjCMakeClass

image.png

  • 创建类。

GSObjCAddClasses

image.png

  • 注册类

5.1.3 overrideSetterFor setter方法处理

image.png

可以看到对set以及_set做了处理。
for循环中有如下代码:
image.png

对特殊类型做了处理,那么setter中做了什么处理呢?
image.png

  • 根据是否开启自动回调决定是否调用willChangeValueForKey以及didChangeValueForKey

didChangeValueForKey

image.png

最终调用了notifyForKey发送通知。

notifyForKey:ofInstance:prior:

image.png

最终调用了观察者的observeValueForKeyPath方法进行回调。

5.2 removeObserver

image.png
  • 移除观察者。
  • 指回isa指针。
    GSKVOBasedealloc中也进行了isa指回:
    image.png
  • GSKVOBasedealloc被同步到了创建的kvo子类中。也就是说在kvo子类实例进行dealloc的时候也会指回isa指针。

至此gnustep中的注册,移除以及回调逻辑就完全走通了。

KVODemo

你可能感兴趣的:(Key-Value Observing(kvo)二:自定义kvo)