iOS 通过RunTime重写KVO

KVO原理:当一个对象被观察时, 系统会新建一个子类NSNotifying_A ,在子类中重写了对象被观察属性的 set方法, 并且改变了该对象的 isa 指针的指向(指向了新建的子类) , 当属性的值发生改变了, 会调用子类的set方法, 然后发出通知

一. 创建NSObject分类, 创建类方法和回调用的block

typedef void(^IBKVOBlock)(NSDictionary *_Nonnull dictionary);
@interface NSObject (IBKVO)
+ (void)IB_AddObserverWithKeyPath:(NSString *)keypath option:(NSKeyValueObservingOptions)option block:(IBKVOBlock)block;
@end

二. 具体代码以及注解

#import "NSObject+IBKVO.h"
#import 
#import 

static const char *IBKVO_getter = "IBKVO_getter";
static const char *IBKVO_setter = "IBKVO_setter";
static const char *IBKVO_block = "IBKVO_block";

@implementation NSObject (IBKVO)

+ (void)IB_AddObserverWithKeyPath:(NSString *)keypath option:(NSKeyValueObservingOptions)option block:(IBKVOBlock)block
{
    //创建子类 默认子类格式:'IBKVO'+'Notifying_'+ClassName
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"IBKVONotifying_%@",oldClassName];
    
    //判断子类是否存在 不存在就创建一个并注册
    Class subClass = objc_getClass(newClassName.UTF8String);
    if (!subClass) {
        subClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
        objc_registerClassPair(subClass);
    }
    
    //将keypath 首字母大写 拼成setValue方法名
    NSString *KeyPath = [[keypath substringToIndex:1] uppercaseString];
    NSString *setKeyPath = [@"set" stringByAppendingString:KeyPath];
    
    //判断属性存不存在
    Method setM = class_getInstanceMethod(subClass, NSSelectorFromString(setKeyPath));
    if (!setM) {
        @throw [NSExpression expressionWithFormat:@"属性不存在"];
    }
    
    SEL setSel = NSSelectorFromString([setKeyPath stringByAppendingString:@":"]);
    
    //添加set方法------
    //先找到本来的set方法的type
    Method setMethod = class_getInstanceMethod([self class], NSSelectorFromString(setKeyPath));
    const char* setTypes = method_getTypeEncoding(setMethod);
    
    //添加自定义的set方法 监听set方法的调用 从而block回调
    class_addMethod(subClass, setSel, (IMP)setMethod, setTypes);
    
    //将self指向子类
    object_setClass(self, subClass);
    
    //保存set get方法名
    objc_setAssociatedObject(self, IBKVO_setter, setKeyPath, OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, IBKVO_getter, keypath, OBJC_ASSOCIATION_COPY_NONATOMIC);
    //保存block
    objc_setAssociatedObject(self, IBKVO_block, block, OBJC_ASSOCIATION_COPY);
}

void setMethod(id self, SEL _cmd,id newValue)
{
    //获取getter setter 名
    NSString *setterName = objc_getAssociatedObject(self, IBKVO_setter);
    NSString *getterName = objc_getAssociatedObject(self, IBKVO_getter);
    
    //保存subclass
    Class subClass = [self class];
    
    //self(isa) 指向super
    object_setClass(self, class_getSuperclass(subClass));
    
    //获取更改前的值 必须让self指向super才能获取
    id oldValue = objc_msgSend(self, NSSelectorFromString(getterName));
    //调用setter 赋给新的值
    objc_msgSend(self, NSSelectorFromString([setterName stringByAppendingString:@":"]),newValue);
    
    //记录前后改变的值
    NSMutableDictionary *change = [NSMutableDictionary new];
    if (newValue) {
        change[NSKeyValueChangeNewKey] = newValue;
    }
    if (oldValue) {
        change[NSKeyValueChangeOldKey] = oldValue;
    }
    
    //返回给调用者 类似于delegate
    IBKVOBlock block = objc_getAssociatedObject(self, IBKVO_block);
    if (block) {
        block(change);
    }
    // isa 指向子类
    object_setClass(self, subClass);
}
@end

你可能感兴趣的:(iOS 通过RunTime重写KVO)