利用runtime实现KVO

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:(NSDictionaryid> *)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_第1张图片

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

利用runtime实现KVO_第2张图片

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

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/

你可能感兴趣的:(iOS)