对于iOS 开发者来说,KVO(key-value-observing)的使用大家已经不再陌生,而且使用起来也是非常方便。
KVO的简单使用:
KVOObject *object = [[KVOObject alloc] init];
[object addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
object.name = @"123";
这样我们就已经为object的name属性添加了监听,只要object的name属性发生改变,我们就可以通过KVO的回调方法获取其新值。
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"新值为 %@", [change objectForKey:NSKeyValueChangeNewKey]);
}
2017-12-18 16:44:36.096356+0800 KVO[5973:274888] 新值为 123
KVO的基本使用就是这样,那么KVO是用什么原理实现的呢?如何自己实现一个带block的KVO呢?
KVO原理:例如在为Object类添加监听时,苹果动态的为我们添加了一个类,类的名字是NSKVONotifying_Object,并且NSKVONotifying_Object是Object的子类,然后把指向Object的类指向了NSKVONotifying_Object,然后在子类中重写setter方法。
直接上代码:
-(void)sp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context callBack:(void (^)(id _Nullable))block{
NSString *className = [@"SP_" stringByAppendingString:NSStringFromClass([self class])];
Class newClass = objc_allocateClassPair([self class], className.UTF8String, 0);//动态生成一个类,类名在原类基础上加一个前缀SP_
objc_registerClassPair(newClass);//注册该类
object_setClass(self, newClass);//把指针指向子类
class_addMethod(newClass, @selector(setName:), (IMP)classSetName, "v@:@");//重写set方法
objc_setAssociatedObject(self, &blockKey, block, OBJC_ASSOCIATION_RETAIN_NONATOMIC);//关联block对象
}
void classSetName(id self,SEL _cmd, NSString * newName){
struct objc_super superClass = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
// 调用父类中setter方法
objc_msgSendSuper(&superClass,_cmd,newName);
void(^block)(id paramter) = objc_getAssociatedObject(self, &blockKey);
if (block) {
block(newName);
}
}
viewController中调用:
@interface ViewController ()
@property (nonatomic, strong) KVOObject * object;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
KVOObject *object = [KVOObject new];
[object sp_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil callBack:^(id _Nullable paramter) {
NSLog(@"block回调: %@\nobject name属性值: %@",paramter,object.name);
}];
_object = object;
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
static int a = 0;
a++;
_object.name = [NSString stringWithFormat:@"%d",a];
}
KVO[6404:296022] block回调: 1
object name属性值: 1
2017-12-18 17:05:34.432499+0800 KVO[6404:296022] block回调: 2
object name属性值: 2