iOS进阶之runtime(2)--KVO简单模拟实现

前言:


上篇简单介绍了下runtime的几个API和KVO的底层原理,现在开始进入正题,来利用这几个API简单的模拟下KVO的原理.大概的步骤就是.

1.在注册KVO的时候注册一个通知,并且替换set方法.

2.然后在我们的set方法里调用原有的set方法,好继续执行原有set方法的逻辑,比如赋值等.

3.然后在我们替换的set方法里判断值是否发生变化,如果发生变化就发送一个通知执行我们特定的观察者方法.

好了不多说,直接贴代码. demo下载请戳我

- (void)zxp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
    //进行KVO监听的时候,注册一个通知
    [[NSNotificationCenter defaultCenter] addObserver:observer
                                             selector:@selector(sendNotification:)
                                                 name:[self notificationNameWithkeyPath:keyPath]
                                               object:nil];
    
//设置关联,,把需要监听的KVO属性(path)保存起来
    objc_setAssociatedObject(self, @selector(getKeyPathString), keyPath, OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, @selector(getKeyValueOptions), @(options), OBJC_ASSOCIATION_ASSIGN);
    //得要这个监听属性的set方法
    NSString *setMethodString = [NSString stringWithFormat:@"set%@%@:",[keyPath substringToIndex:1].uppercaseString,[keyPath substringFromIndex:1]];
    //把 原有的set方法替换成我们的set方法
    method_exchangeImplementations(class_getInstanceMethod([self class], NSSelectorFromString(setMethodString)), class_getInstanceMethod([NSObject class], @selector(newSetMethod:)));

}
//移除观察者
- (void)zxp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
    [[NSNotificationCenter defaultCenter] removeObserver:observer
                                                    name:[self notificationNameWithkeyPath:keyPath]
                                                  object:observer];
}
//观察者的值发生变化的时候执行,需要监听的对象重写此方法,否则抛出异常
- (void)zxp_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    NSAssert(NO, @"需要实现方法:%@",NSStringFromSelector(@selector(zxp_observeValueForKeyPath:ofObject:change:context:)));
}

#pragma mark - private

- (NSString *)notificationNameWithkeyPath:(NSString *)keyPath {
    return [NSString stringWithFormat:@"%zi,%@,%@",self,NSStringFromSelector(@selector(notificationNameWithkeyPath:)),keyPath];
}
//我们自己实现的set方法,在set方法里判断监听的属性是否发生变化,如果发生变化就发送一个通知
- (void)newSetMethod:(id)params {
    id value = [self valueForKeyPath:[self getKeyPathString]];
    if (value != params && [self getKeyValueOptions] == NSKeyValueObservingOptionNew) {
        [[NSNotificationCenter defaultCenter] postNotificationName:[self notificationNameWithkeyPath:[self getKeyPathString]] object:nil];
    }
    [self newSetMethod:params];
}

- (NSString *)getKeyPathString {
    return objc_getAssociatedObject(self, @selector(getKeyPathString));
}

- (NSKeyValueObservingOptions)getKeyValueOptions {
    return [objc_getAssociatedObject(self, @selector(getKeyValueOptions)) integerValue];
}

#pragma mark - notification methods
//通知的方法,如果收到此通知就执行zxp_observeValueForKeyPath:ofObject: change: context: 方法                                           



- (void)sendNotification:(NSNotification *)notification {
#warning 没做传参,随便用什么保存这几个参数存进来就行了,比如AssociatedObject里
    [self zxp_observeValueForKeyPath:nil ofObject:nil change:nil context:nil];
}


posted @ 2015-11-29 11:20  张孝平  阅读(197)  评论(0编辑  收藏  举报