自定义KVO

1. 不调用实例变量的方法

2. 动态生成子类 (利用runtime生成:申请类,添加一些方法-set-class等方法,注册类 )

****常量类型不能添加观察者

 

#import <Foundation/Foundation.h>

 

NS_ASSUME_NONNULL_BEGIN

 

@interface NSObject (FXKVO)

 

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

 

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

 

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

@end

 

NS_ASSUME_NONNULL_END

 

添加一个NSObject的分类,重写观察者方法

 

#pragma mark - 验证是否存在setter方法,如果不存在抛出异常(例如成员变量添加观察者会崩溃)

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

    //1. 验证set方法

    [self judgeSetterMethodFromKeyPath:keyPath];

    //2. 动态生成子类

    Class newClass = [self creatChildClass];

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

    object_setClass(self, newClass);

   //4. 观察者(为当前类添加属性,来记录观察者,用来做回调的时候使用)

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

}

 

#pragma mark - 动态生成子类

- (Class)creatChildClass{

 // LGKVO_LGPerson

    

    // 2. 动态生成子类

     

    NSString *oldName = NSStringFromClass([self class]);

    NSString *newName = [NSString stringWithFormat:@"%@%@", FXKVOPrefix, oldName];

    Class newClass    = objc_allocateClassPair([self class], newName.UTF8String, 0);

    

    //申请注册到内存中

    objc_registerClassPair(newClass);

    

    // 2.1 子类添加一些方法 class setter

    // class

    SEL classSEL = NSSelectorFromString(@"class");

    Method classM = class_getInstanceMethod([self class], classSEL);

    const char *type = method_getTypeEncoding(classM);

    class_addMethod(newClass, classSEL, (IMP)fx_class, type);

 

    // setter

    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));

    Method setterM = class_getInstanceMethod([self class], setterSEL);

    const char *settertype = method_getTypeEncoding(setterM);

    class_addMethod(newClass, setterSEL, (IMP)fx_setter, settertype);

    

    return newClass;

}

 

#pragma mark - 函数部分

static void fx_setter(id self, SEL _cmd, id newValue) {

 

    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));

 

    // 消息发送 setName:

    [self willChangeValueForKey:keyPath];

 

    // newValue给谁, newClass KVOPerson

    // 给父类发送消息

    void (*fx_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;

    struct objc_super fx_objc_super = {

        .receiver = self,

        .super_class = [self class],

    };

    fx_msgSendSuper(&fx_objc_super, _cmd,newValue);

 

    [self didChangeValueForKey:keyPath];

 

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

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

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

    //响应

    //sel

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

    ((id (*)(id, SEL, id, id, id, id))objc_msgSend)(observer, obserSEL, keyPath,self,@{keyPath:newValue},NULL);

}

 

//注意这里不能写成class_getSuperclass([self class]); //会递归崩溃 [self class]的底层imp是 <=> class_getSuperclass([self class]) 所以会无限递归

Class fx_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 - 验证是否存在setter方法

- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{

    Class superclass = object_getClass(self);

    SEL setterSelector = NSSelectorFromString(setterForGetter(keyPath));

    Method setterMethod = class_getInstanceMethod(superclass, setterSelector);

    if(!setterMethod) {

        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"老铁没有当前%@的setter", keyPath] userInfo:nil];

    }

}

 

#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];

}

 

运行结果:(与系统KVO打印信息相同)

目前还存在的问题:

1. 只能添加一个观察者对象 

2. 目前考虑是否可以使用block回调(代码逻辑和功能逻辑放到一起)

3. 每次添加网观察者还需要手动移除,是否可以实现自动移除

后面还需要优化(在下一篇博客中优化)

4. 为什么要生成动态子类,并且重写方法(谈下自己的理解,互不影响,是一中设计,这个动态子类是随时都会消失,是一个临时的子类,减少对原类的入侵,不影响本类,就像我们会新建子类处理一些子类的逻辑,而不影响父类一样)

 比较成功的KVO框架(FBKVO)

posted @ 2019-12-21 19:57  do+better  阅读(302)  评论(0编辑  收藏  举报