iOS 利用runtime手动实现KVO

KVO原理:调用监听对象属性的方法,动态创建一个继承自该对象所属类的子类,然后重写该属性的setter方法,在setter方法类调用willChangeValueForKey:didChangeValueForKey:方法来触发observeValueForKeyPath:ofObject:change:context:方法。
大概看了一下,大多数手动实现KVO都并没有做到真正动态创建类,而是手动。本文主要是动态创建继承类动态重写setter方法

直接贴代码吧,注释都在代码里面。
首先创建NSObject分类NSObject+XKVO暴露接口:
.h

#import 

@interface NSObject (XKVO)

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

@end

.m

#import "NSObject+XKVO.h"
#import 
#import 

@implementation NSObject (XKVO)

// 属性:用于保存监听对象
static char *kObserver = "kObserver";
- (void)setObserver:(id)observer {
    objc_setAssociatedObject(self, kObserver, observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)observer {
    return objc_getAssociatedObject(self, kObserver);
}

void setterMethod(id self, SEL _cmd, id obj) {
    
    /* 根据_cmd和属性列表获取属性名和属性对应的old value数据 */
    NSString *name = NSStringFromSelector(_cmd);
    NSString *keyPath = [[name lowercaseString] substringFromIndex:3];
    keyPath = [keyPath substringToIndex:keyPath.length-1];
    
    id oldValue ;
    
    unsigned int count ;
    Ivar *ivarlist = class_copyIvarList([self superclass], &count);
    for (int i = 0; i < count; i++) {
        Ivar thisivar = ivarlist[i];
        NSString *name = [NSString stringWithUTF8String:ivar_getName(thisivar)];
        NSString *properName = [name substringFromIndex:1];
        name = [[name lowercaseString] substringFromIndex:1];
        BOOL isEqual = [name isEqualToString:keyPath];
        if (isEqual) {
            keyPath = properName;
            oldValue = [self valueForKey:keyPath];
            break;
        }
    }
    
    /* 调用父类的setter方法,保证设置数据不被干扰 */
    Method sm = class_getInstanceMethod([self superclass], _cmd);
    IMP imp = method_getImplementation(sm);
    imp(self, _cmd, obj);
    
    id obs = objc_getAssociatedObject(self, kObserver);
    if (obs) {
        /* 调用监听对象实现的方法 */
        objc_msgSend(obs, @selector(observeValueForKeyPath:ofObject:change:context:), keyPath, self, @{@"NSKeyValueObservingOptionNew":obj, @"NSKeyValueObservingOptionOld":oldValue}, nil);
    }
}

- (void)xl_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context  {
    
    /* 保存监听对象 */
    self.observer = observer;
    
    /* 动态创建子类 */
    Class NSObjectKVO = objc_allocateClassPair([self class], "NSObjectKVO", 0);
    
    /* 获取setter方法名 */
    NSString*setterName=[NSString stringWithFormat:@"set%@%@:",[[keyPath substringToIndex:1] uppercaseString],[keyPath substringFromIndex:1]];
    
    /* 为子类动态添加方法 */
    BOOL success = class_addMethod(NSObjectKVO, NSSelectorFromString(setterName), (IMP)setterMethod, "V@:");
    if (success) {
        /* 将当前对象指向新建的子类 */
        object_setClass(self, NSObjectKVO);
    }else{
        
    }
    
}

@end

很简单的实现,demo就不上传github了,放在了CSDN,有兴趣多支持一下。

你可能感兴趣的:(iOS 利用runtime手动实现KVO)