前言
这篇写一下KVO的原理及手动实现
正文
先看这样一段代码
isa
是真实类型,class
是系统提供方法获取的类型。可见,对person进行观察后,从第三句输出可以看到,person的真实类型发生了改变,并且中间类继承自原类,即NSKVONtifying_Person -> Person
,而苹果欺骗了我们。结合前文写过的KVO内部通知,可以来看看KVO究竟做了那些事
- 重写被观察对象属性对应的
setter
- 改变被观察对象的真实类型,且与原类为继承关系
- 重写被观察对象的
- (Class)class
知道苹果都干了啥,自己就可以开工了。
这里模仿如何自己动手实现 KVO写了一份代码
首先调试一下环境
@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
@end
@implementation Person
- (instancetype)init {
if (self = [super init]) {
_name = @"default";
}
return self;
}
@end
简单的搞个Person
类,这没啥好说的
然后搞个NSObject
的分类(利用block回调更方便,个人也不是很习惯苹果的API)
typedef void(^JKKVOBlock)(id observedObj, NSString *observedKey, id oldValue, id newValue);
@interface NSObject (JKKVO)
- (void)jk_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
- (void)jk_addObserver:(NSObject *)observer forKey:(NSString *)key withBlock:(JKKVOBlock)block;
@end
#import "NSObject+JKKVO.h"
#import <objc/objc-runtime.h>
NSString *const JKKVOClassPrefix = @"JKKVONotifying_";
const char * JKKVOAssociateKey;
@interface JKObserverInfo : NSObject
@property (weak, nonatomic) NSObject *observer;
@property (copy, nonatomic) NSString *key;
@property (copy, nonatomic) JKKVOBlock block;
@end
接着就是最重要的部分,实现KVO。文中关键部分有注释,耐心点看应该不难懂
@implementation JKObserverInfo
- (instancetype)initWithObserver:(NSObject *)observer Key:(NSString *)key block:(JKKVOBlock)block {
if (self = [super init]) {
_observer = observer;
_key = key;
_block = block;
}
return self;
}
@end
@implementation NSObject (JKKVO)
- (NSString *)setterFromKey:(NSString *)key {
NSMutableString *mutableKey = [NSMutableString stringWithString:key];
[mutableKey replaceCharactersInRange:NSMakeRange(0, 1) withString:[[key substringToIndex:1] uppercaseString]];
return [NSString stringWithFormat:@"set%@:", mutableKey];
}
- (Class)kvoObservedClassFromOriginClass:(Class)cls {
NSString *clsStr = NSStringFromClass(cls);
NSString *newClsStr = [JKKVOClassPrefix stringByAppendingString:clsStr];
Class newCls = NSClassFromString(newClsStr);
if (newCls) return newCls;
newCls = objc_allocateClassPair(cls, newClsStr.UTF8String, 0);
[self rewriteClassMethodWithNewClass:newCls];
objc_registerClassPair(newCls);
return newCls;
}
- (void)rewriteClassMethodWithNewClass:(Class)newCls {
Method clsMethod = class_getInstanceMethod([self class], @selector(class));
const char *types = method_getTypeEncoding(clsMethod);
class_addMethod(newCls, @selector(class), (IMP)newClass, types);
}
Class newClass(id self, SEL _cmd) {
return class_getSuperclass(object_getClass(self));
}
- (void)addObservers:(NSObject *)obs {
// 设置关联属性用于添加观察者(因为在分类中,不能直接设置属性)
NSMutableArray *observers = objc_getAssociatedObject(self, JKKVOAssociateKey);
if (!observers) {
observers = [NSMutableArray array];
objc_setAssociatedObject(self, JKKVOAssociateKey, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[observers addObject:obs];
}
void newSetter(id self, SEL _cmd, id newValue) {
NSString *setter = NSStringFromSelector(_cmd);
NSString *getter = [self getterFromSetter:setter];
if (!getter) {
NSString *reason = [NSString stringWithFormat:@"找不到%@对应属性%@的setter", self, getter];
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil];
return;
}
NSString *key = [[getter componentsSeparatedByString:@":"] firstObject];
id oldValue = [self valueForKey:key];
struct objc_super supercls = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
objc_msgSendSuper(&supercls, _cmd, newValue);
NSMutableArray *observers = objc_getAssociatedObject(self, JKKVOAssociateKey);
for (JKObserverInfo *obs in observers) {
if ([obs.key isEqualToString:key]) {
obs.block(self, key, oldValue, newValue);
}
}
}
- (NSString *)getterFromSetter:(NSString *)setter {
NSString *getter = [setter substringFromIndex:3];
NSString *firstLow = [getter substringToIndex:1].lowercaseString;
return [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstLow];
}
- (void)jk_addObserver:(NSObject *)observer forKey:(NSString *)key withBlock:(JKKVOBlock)block {
// 获取观察对象属性的setter
NSString *setter = [self setterFromKey:key];
SEL setterS = NSSelectorFromString(setter);
Method setterM = class_getInstanceMethod([self class], setterS);
if (!setterM) {
NSString *reason = [NSString stringWithFormat:@"找不到%@对应属性%@的setter", self, key];
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil];
return;
}
// 生成中间类并重写 - (Class)class
Class newCls = [self kvoObservedClassFromOriginClass:[self class]];
// self 重定向
object_setClass(self, newCls);
// 重写setter
const char *types = method_getTypeEncoding(setterM);
class_addMethod(newCls, setterS, (IMP)newSetter, types);
// 添加观察者
JKObserverInfo *observerInfo = [[JKObserverInfo alloc] initWithObserver:observer Key:key block:block];
[self addObservers:observerInfo];
}
- (void)jk_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
NSMutableArray *observers = objc_getAssociatedObject(self, JKKVOAssociateKey);
for (JKObserverInfo *obsInfo in observers) {
if (obsInfo.observer == observer && [obsInfo.key isEqualToString:keyPath]) {
[observers removeObject:obsInfo];
break;
}
}
}
@end
OK,大功告成!来测试一下吧
然而并没有什么卵用,有些东西自己写着玩就好,要么就搞个开源框架,否则你永远不知道自己的代码有多烂😔