欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

利用runtime实现KVO

程序员文章站 2024-03-24 13:59:34
...

KVO实现原理

一.关于KVO

KVO(Key-Value Observing)提供一种机制,当指定对象的属性被修改后,就会通知观察者。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。

KVO其实也是“观察者”设计模式的一种应用。这种模式有利于两个类间的解耦合,尤其是对于业务逻辑与视图控制这两个功能的解耦合。

二.KVO用法

假设已经有一个Person类,包含name属性,我们可以通过KVO监听到name属性被修改的事件:

_p = [Person new];    
[_p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];

即当_p对象的name属性被修改后,会调用self的以下方法:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"kvo监听到%@对象的%@属性被修改为%@",object,keyPath,[change objectForKey:@"new"]);
}

三.KVO实现原理

当调用以下函数前:

[_p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];

我们可以看到_p的isa指针指向类Person:

利用runtime实现KVO

但是当单步执行过后,_p的isa指针会变为指向NSKVONotifying_Person类

利用runtime实现KVO

即对于上面的代码,系统会执行如下操作:

1、利用runtime动态创建Person类的子类NSKVONotifying_Person,重写name属性的set方法:

-(void)setName:(NSString*)name{
[self willChangeValueForKey:@"name"];
[super setName:name];
[self didChangeValueForKey:@"name"];
}

2、将p对象的isa指针指向新建的子类

当我们修改_p的name属性时,会执行NSKVONotifying_Person类对象的set方法。

四.自己实现KVO

我们可以按照上面的思路,自己用代码实现KVO的基本功能,同时优化为以block的方式进行回调而非使用-observeValueForKeyPath:ofObject:change:context:方法。

1.新建一个NSObjct的Category,声明添加监听和移除监听方法:
NSObject+KVO.h

typedef void(^JXObserveingBlock)(id observer,NSString *key,id oldValue,id newValue);

@interface NSObject (KVO)

-(void)JX_addObserver:(NSObject *)observer forKeyPath:(NSString *)key block:(JXObserveingBlock*)block;

-(void)JX_removeObserver:(NSObject *)observer forKey:(NSString *)key;

@end

2.实现JX_addObserver:forKeyPath:block:方法:

-(void)JX_addObserver:(NSObject *)observer forKeyPath:(NSString *)key block:(JXObserveingBlock)block{
    //1.检查对象是否存在对应的set方法,没有则抛出异常。
    SEL setterSelector = NSSelectorFromString([self setterForGetter:key]);
    Method setterMethod = class_getInstanceMethod([self class], setterSelector);
    if (!setterMethod) {
        // throw invalid argument exception
    }

    //2.检查isa指针指向的类是否已经是KVO的类,不是则新建原类的子类,并把isa指针指向新类。
    Class class = object_getClass(self);
    NSString *className = NSStringFromClass(class);
    if (![className hasPrefix:kJXKVOClassPrefix]) {
        class = [self makeKvoClassWithOriginalClassName:className];
        object_setClass(self, class);
    }

    //3.检查KVO类是否已经重写了set方法,没有则重写set方法。
    if (![self hasSelector:setterSelector]) {
        const char *types = method_getTypeEncoding(setterMethod);
        class_addMethod(class, setterSelector, (IMP)kvo_setter, types);
    }

    //添加观察者
    JXObservationInfo *info = [[JXObservationInfo alloc] initWithObserver:observer Key:key block:block];
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void*)kJXKVOAssociatedObservers);
    if (!observers) {
        observers = [NSMutableArray array];
        objc_setAssociatedObject(self, (__bridge const void*)kJXKVOAssociatedObservers, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [observers addObject:info];
}

其中setterForGetter:方法是获取属性对应的set方法名:

-(NSString*)setterForGetter:(NSString*)key{
    if (key.length <= 0) {
        return nil;
    }

    //1.首字母大写
    key = [key capitalizedStringWithLocale:[NSLocale currentLocale]];

    //2.添加set前缀和:后缀
    NSString *setter = [NSString stringWithFormat:@"set%@:",key];
    return setter;
}

hasSelector:方法判断是否已经重写了set方法

- (BOOL)hasSelector:(SEL)selector{
    Class clazz = object_getClass(self);
    unsigned int methodCount = 0;
    Method* methodList = class_copyMethodList(clazz, &methodCount);
    for (unsigned int i = 0; i < methodCount; i++) {
        SEL thisSelector = method_getName(methodList[i]);
        if (thisSelector == selector) {
            free(methodList);
            return YES;
        }
    }

    free(methodList);
    return NO;
}

makeKvoClassWithOriginalClassName:方法根据类名返回kvo类:

- (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName{
    NSString *kvoClassName = [kJXKVOClassPrefix stringByAppendingString:originalClazzName];
    Class clazz = NSClassFromString(kvoClassName);

    if (clazz) {
        return clazz;
    }

    //如果还没创建对应的kvo类,则创建
    Class originalClazz = object_getClass(self);
    Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClassName.UTF8String, 0);

    //重写class方法
    Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));

    const char *types = method_getTypeEncoding(clazzMethod);
    class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);

    objc_registerClassPair(kvoClazz);

    return kvoClazz;
}

static Class kvo_class(id self, SEL _cmd)
{
    return class_getSuperclass(object_getClass(self));
}

最后,重写setter 方法:

static void kvo_setter(id self, SEL _cmd, id newValue)
{
    NSString *setterName = NSStringFromSelector(_cmd);
    NSString *getterName = getterForSetter(setterName);

    if (!getterName) {
        NSString *reason = [NSString stringWithFormat:@"Object %@ does not have setter %@", self, setterName];
        @throw [NSException exceptionWithName:NSInvalidArgumentException
                                       reason:reason
                                     userInfo:nil];
        return;
    }

    id oldValue = [self valueForKey:getterName];

    struct objc_super superclazz = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };

    // cast our pointer so the compiler won't complain
    void (*objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper;

    // call super's setter, which is original class's setter method
    objc_msgSendSuperCasted(&superclazz, _cmd, newValue);

    // look up observers and call the blocks
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kJXKVOAssociatedObservers));
    for (JXObservationInfo *each in observers) {
        if ([each.key isEqualToString:getterName]) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                each.block(self, getterName, oldValue, newValue);
            });
        }
    }
}

static NSString * getterForSetter(NSString *setter)
{
    if (setter.length <=0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) {
        return nil;
    }

    // 移除‘set’前缀以及后面的‘:’
    NSRange range = NSMakeRange(3, setter.length - 4);
    NSString *key = [setter substringWithRange:range];

    // 首字母小写
    NSString *firstLetter = [[key substringToIndex:1] lowercaseString];
    key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1)
                                       withString:firstLetter];

    return key;
}

五.测试实现的KVO

把原来的:

[_p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];

修改为:

[_p JX_addObserver:self forKeyPath:@"name" block:^(id observer, NSString *key, id oldValue, id newValue) {
        NSLog(@"kvo监听到%@对象的%@属性被修改为%@",observer,key,newValue);
    }];

在修改_p的name属性之后,会打印出修改信息。

完整代码下载:http://download.csdn.net/detail/dolacmeng/9849000

参考:http://tech.glowing.com/cn/implement-kvo/