你们都知道kvo是一种设计模式,是一种键值观察,当属性的值改变时候会触发回调,获取该属性的旧值和新值。可是可能有些朋友不清楚何时用它,使用场景是什么。当须要监听一个属性的值改变时候咱们能够用到它。好比:git
//注册kvo
[p1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
//属性赋值
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[super touchesBegan:touches withEvent:event];
count++;
p1.name = [NSString stringWithFormat:@"%d",count];
}
//释放
- (void)dealloc{
[p1 removeObserver:self forKeyPath:@"name"];
}
复制代码
//重写Person类的automaticallyNotifiesObserversForKey返回NO即关闭了自动kvo
@implementation Person
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
return NO;
}
@end
//注册kvo
[p1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
//属性值变化(其实只要该对象的成员变量的值改变便可)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[super touchesBegan:touches withEvent:event];
count++;
[p1 willChangeValueForKey:@"name"];
p1.name = [NSString stringWithFormat:@"%d",count];
[p1 didChangeValueForKey:@"name"];
}
//kvo回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
}
//释放
- (void)dealloc{
[p1 removeObserver:self forKeyPath:@"name"];
}
复制代码
系统的kvo是怎么实现的呢?为何只要对象的属性变化了就会触发回调呢?我也很好奇,在查看资料以前,本身也考虑了一下怎么实现。
首先系统的kvo任意一个对象均可以调用addObserver方法,能够肯定应该是NSObject的分类,并新增了这个addObserver的方法。接下来想到的是hook,好比hook Person类对应的setter方法。利用runTime方法交换实如今调用这个setter方法以前获取该属性对应的成员变量的值,获得旧值。以后再调用该setter方法以后再次获取该属性对应的成员变量的值,获得新值。最后再调用observeValueForKeyPath方法把新旧值传递给observe对应的类。github
接下来查看资料,Oh,My God 并非本身想的那样,系统在调用addObserver方法时候动态的建立了一个新的子类继承该被监听的对象所对应的类。并重写了父类的setter方法。并把对象的isa指针从父类指向了该子类。这样当父类的对象调用setter方法时候就会调用子类的setter方法,在该setter方法内部调用了willChangeValueForKey,didChangeValueForKey方法。以后系统会调用observeValueForKeyPath方法,把旧的和新的值传递给oberver所对应的类。在新的子类里除了重写了父类的setter方法之外还重写了class方法,该方法是为了外界调用class时候隐藏新建立的子类。有一点很奇怪当咱们在addObserver方法后打一个断点时并把鼠标光标移动到改对象上会发现它竟然显示的是父类而不是新生成的子类。按理来讲咱们把isa指针指向新的子类后该对象应该就属于子类的实例才对。设计模式
再以后我来验证了一下,打印一下isa指针指向的类bash
NSLog(@"p1:%@",object_getClass(p1));
[p1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
NSLog(@"p1:%@",object_getClass(p1));
复制代码
打印结果:app
2019-01-29 14:28:40.983065+0800 KVOCustom[20946:172543] p1:Person
2019-01-29 14:28:40.983401+0800 KVOCustom[20946:172543] p1:NSKVONotifying_Person
复制代码
发现确实在addOberver方法调用前是指向了Person类,在调用后指向了新类NSKVONotifying_Person,从而证实了addOberver方法内部确实是建立了新类。
以后固然是想了解下这个系统建立的新的类内部实现了哪些方法啦。本身写了一个打印类内部方法以下:框架
- (void)printMethods:(Class)cls{
unsigned int count;
Method *methods = class_copyMethodList(cls, &count);
NSMutableString *strM = [NSMutableString string];
[strM appendString:[NSString stringWithFormat:@"%@: ",cls]];
for (int i = 0; i < count; i++) {
Method method = methods[i];
NSString *strMethodName = NSStringFromSelector(method_getName(method));
[strM appendString:strMethodName];
[strM appendString:@", "];
}
NSLog(@"%@",strM);
}
复制代码
以后咱们调用方法打印函数
[p1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
[p1 printMethods:object_getClass(p1)];
复制代码
打印结果:ui
2019-01-29 14:35:32.435852+0800 KVOCustom[21016:175547] NSKVONotifying_Person: setName:, class, dealloc, _isKVOA,
复制代码
说明系统新建立的类内部实现了setName方法(用来重写父类的setter方法),class方法(为了隐藏子类),dealloc方法(释放内存),_isKVOA方法(是系统的kvo方法)url
当咱们探索到这里基本上也就请楚了系统的kvo的实现。spa
Class classUse(id self,SEL _cmd){
return class_getSuperclass(object_getClass(self));
}
复制代码
int _isKVOAUse(id self,SEL _cmd){
return YES;
}
复制代码
如今是否是手痒痒想本身手写个KVO啦,如今咱们清楚了系统KVO的实现,模仿它咱们本身实现一个KVO吧。
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath valueChangeBlk:(void(^)(id old, id new))valueChangeBlk{
//建立子类
NSString *oldClass = NSStringFromClass(self.class);
NSString *newClass = [NSString stringWithFormat:@"BSKVONotify_%@",oldClass];
Class classNew = objc_allocateClassPair(self.class, newClass.UTF8String, 16);
objc_registerClassPair(classNew);
object_setClass(self, NSClassFromString(newClass));
//新增set方法
NSMutableString *strM = [NSMutableString string];
[strM appendString:[[keyPath substringToIndex:1] uppercaseString]];
[strM appendString:[keyPath substringFromIndex:1]];
NSString *setMethod = [NSString stringWithFormat:@"set%@:",strM];
class_addMethod(NSClassFromString(newClass), NSSelectorFromString(setMethod), (IMP)keyPathMethod,"v@:@");
//新增class方法
class_addMethod(classNew, NSSelectorFromString(@"class"), (IMP)classUse,"#@:");
//新增_isKVOA方法
class_addMethod(classNew, NSSelectorFromString(@"_isKVOA"),(IMP)_isKVOAUse, "i@:");
//设置关联对象
objc_setAssociatedObject(self, "keyPath", keyPath, OBJC_ASSOCIATION_COPY);
objc_setAssociatedObject(self, "blk", valueChangeBlk, OBJC_ASSOCIATION_COPY);
objc_setAssociatedObject(self, "classNew", classNew, OBJC_ASSOCIATION_RETAIN);
objc_setAssociatedObject(self, "classOld", self.class, OBJC_ASSOCIATION_RETAIN);
}
复制代码
void keyPathMethod(id self,IMP _cmd, id arg){
//set方法名,原始类和子类
NSString *keyPath = objc_getAssociatedObject(self, "keyPath");
NSMutableString *strM = [NSMutableString string];
[strM appendString:[[keyPath substringToIndex:1] uppercaseString]];
[strM appendString:[keyPath substringFromIndex:1]];
NSString *setMethod = [NSString stringWithFormat:@"set%@:",strM];
Class subClass = objc_getAssociatedObject(self, "classNew");
Class oldClass = objc_getAssociatedObject(self,"classOld");
//isa指针指向父类,执行set方法
object_setClass(self, oldClass);
//获取成员变量的值
Ivar ivar = class_getInstanceVariable([self class], [NSString stringWithFormat:@"_%@",keyPath].UTF8String);
id value = object_getIvar(self, ivar);
// NSLog(@"old:%@",value);
((id (*) (id,SEL,id))objc_msgSend)(self,NSSelectorFromString(setMethod),arg);
id valueNew = arg;
// NSLog(@"new:%@",valueNew);
//isa指针指向子类
object_setClass(self, subClass);
void(^blkUse)(id old, id new) = objc_getAssociatedObject(self, "blk");
if (blkUse) {
blkUse(value,valueNew);
}
}
复制代码
以上方法便可实现一个简单的kvo了 3. 重写class方法来隐藏内部子类
Class classUse(id self,SEL _cmd){
return class_getSuperclass(object_getClass(self));
}
复制代码
int _isKVOAUse(id self,SEL _cmd){
return YES;
}
复制代码
上述方法便可实现一个kvo了,拿去用吧!
p1 = [[Person alloc] init];
__weak typeof(self) weakSelf = self;
[p1 addObserver:self forKeyPath:@"name" valueChangeBlk:^(id _Nonnull old, id _Nonnull new) {
typeof(weakSelf) self = weakSelf;
NSLog(@"self:%@,old:%@, new:%@",self,old,new);
}];
复制代码
这里我用__weak typeof(self) weakSelf = self;typeof(weakSelf) self = weakSelf;巧妙的解决了循环引用问题。这里我是参考MJRefresh源码,这样在block内部就能够继续使用self关键字了。 4. 关于以上的疑惑但愿有人能解答,thanks!thanks!thanks!最后附上github上源代码给你们参考:github.com/FreeBaiShun…