仿写KVO的实现
KVO原理参考可前一篇OC的KVO学习记录
代码github地址:Sameny仿写KVO
注:关键代码的解释我放到了代码中,如有疑问,欢饮一起探讨
我们需要一些准备代码,以此来获取setter和getter方法,以及存储observer对象
NSString *const kSTKVOClassPrefix = @"STKVOClass_";
NSString *const kSTKVOObservers = @"STKVOObservers";
@interface STObservedPiece : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) STObservingBlock block;
@end
@implementation STObservedPiece
- (instancetype)initWithObserver:(NSObject *)observer key:(NSString *)key block:(STObservingBlock)block
{
self = [super init];
if (self) {
_observer = observer;
_key = key;
_block = block;
}
return self;
}
@end
// 构造setter的方法名
static NSString * getSetterSelector(NSString *key) {
if (key.length <= 0) {
return nil;
}
NSString *firstLetter = [[key substringToIndex:1] uppercaseString];
NSString *restLetter = [key substringFromIndex:1];
NSString *setter = [NSString stringWithFormat:@"set%@%@:", firstLetter, restLetter];
return setter;
}
static NSString * getGetterSelector(NSString *setter) {
if (setter.length <=0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) {
return nil;
}
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,核心代码如下:
首先构造我们所需的KVO子类
- (Class)createKVOClassWithObserveredClassName:(NSString *)observeredClassName {
NSString *kvo_class_name = [kSTKVOClassPrefix stringByAppendingString:observeredClassName];
Class kvo_class = NSClassFromString(kvo_class_name);
if (kvo_class) {
return kvo_class;
}
// 如果还没有构造过,就开始构造STKVOClass_XXX类
// 我们使用构造observeredClass类的一个子类,该函数定义如下
/*
* Creates a new class and metaclass.
* OBJC_EXPORT Class _Nullable
* objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, size_t extraBytes)
* 该函数是比较直观的,直接返回创建好的类
*/
Class observeredClass = object_getClass(self);
kvo_class = objc_allocateClassPair(observeredClass, kvo_class_name.UTF8String, 0);
// 重写class方法实现,使其返回父类的Class
Method classMethod = class_getInstanceMethod(kvo_class, @selector(class));
const char * types = method_getTypeEncoding(classMethod);
class_addMethod(kvo_class, @selector(class), (IMP)kvo_class_imp, types);
// 创建类之后需要register类
objc_registerClassPair(kvo_class);
return kvo_class;
}
kvo_class_imp函数实现如下:
static Class kvo_class_imp(id self, SEL _cmd)
{
return class_getSuperclass(object_getClass(self));
}
其次构造我们的子类的setter方法
// 构造setter方法
static void st_kvo_setter(id self, SEL _cmd, id newValue) {
NSString *setterName = NSStringFromSelector(_cmd);
NSString *getterName = getGetterSelector(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];
// ** 获取父类,并通过objc_msgSendSuper调用父类的setter方法更新值
struct objc_super superclass = {
.receiver = self, // 这个self就是父类的实例,消息的接受者
.super_class = class_getSuperclass(object_getClass(self))
};
// OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
/*
* These functions must be cast to an appropriate function pointer type
* before being called.
* 该函数需要映射到一个函数指针,而且其参数个数也是看需要而定,我们这里有3个参数,一个消息的接受者superclass,所调用的方法setter,参数是newValue
*/
// 哈哈,这个像定义Block块一样,是不是?
void(*st_objc_msgSendSuperCasted)(void*, SEL, id) = (void *)objc_msgSendSuper;
// 调用执行父类setter方法,更新父类被监听对象的值
st_objc_msgSendSuperCasted(&superclass, _cmd, newValue);
// 执行完数值更新后,就该通知监听的观察者了
NSMutableArray <STObservedPiece *>*observerPieces = objc_getAssociatedObject(self, (__bridge const void *)(kSTKVOObservers));
[observerPieces enumerateObjectsUsingBlock:^(STObservedPiece * _Nonnull piece, NSUInteger idx, BOOL * _Nonnull stop) {
if ([piece.key isEqualToString:getterName]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
piece.block(self, getterName, oldValue, newValue);
});
}
}];
}
接下来就是我们的对外接口
- (void)st_addObserver:(NSObject *)observer forKey:(NSString *)key withBlock:(STObservingBlock)block {
SEL setterSelector = NSSelectorFromString(getSetterSelector(key));
Method setterMethod = class_getInstanceMethod([self class], setterSelector);
if (!setterMethod) {
NSString *reason = [NSString stringWithFormat:@"Object %@ does not have a setter for key %@", self, key];
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:reason
userInfo:nil];
return;
}
Class class = object_getClass(self);
NSString *className = NSStringFromClass(class);
if (![className hasPrefix:kSTKVOClassPrefix]) {
class = [self createKVOClassWithObserveredClassName:className];
// 更改isa指针,将self的isa指针指向子类,这样当self的setter方法执行的时候,调用的是子类的setter,从而实现了在子类setter中进行通知的功能
object_setClass(self, class);
}
// 判断子类(因为isa指针指向了子类)是否实现了setter方法
if (![self hasSelector:setterSelector]) {
const char * types = method_getTypeEncoding(setterMethod);
class_addMethod(class, setterSelector, (IMP)st_kvo_setter, types);
}
// 存储通知的内容
STObservedPiece *piece = [[STObservedPiece alloc] initWithObserver:observer key:key block:block];
NSMutableArray <STObservedPiece *>*observerPieces = objc_getAssociatedObject(self, (__bridge const void *)(kSTKVOObservers));
if (!observerPieces) {
observerPieces = [[NSMutableArray alloc] init];
objc_setAssociatedObject(self, (__bridge const void *)(kSTKVOObservers), observerPieces, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[observerPieces addObject:piece];
}
KVO有addObserver方法,当然也要有removeObserver方法了:
- (void)st_removeObserver:(NSObject *)observer forKey:(NSString *)key {
NSMutableArray <STObservedPiece *>*observerPieces = objc_getAssociatedObject(self, (__bridge const void *)(kSTKVOObservers));
if (observerPieces) {
if (!observer) {
[observerPieces removeAllObjects]; // 移除所有监听
}
else {
__block NSMutableArray <STObservedPiece *>*removePieces = [[NSMutableArray alloc] init];
[observerPieces enumerateObjectsUsingBlock:^(STObservedPiece * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj.observer == observer) {
if (key.length > 0) {
if ([obj.key isEqualToString:key]) {
[removePieces addObject:obj];
*stop = YES;
}
}
else {
[removePieces addObject:obj];
}
}
}];
[observerPieces removeObjectsInArray:removePieces];
}
// 销毁我们创建的STKVOClass_XXX类,并将self的isa指针重置为原来的类
if (observerPieces.count == 0) {
Class kvoClass = object_getClass(self);
if ([NSStringFromClass(kvoClass) hasPrefix:kSTKVOClassPrefix]) { // 防止多次删除
Class originalClass = class_getSuperclass(kvoClass);
object_setClass(self, originalClass);
objc_disposeClassPair(kvoClass);
}
}
}
}
我们来测试一下结果:
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSMutableArray <NSString *>*nicks;
@property (nonatomic, assign) NSInteger age;
@end
@implementation Person
+ (instancetype)personWithName:(NSString *)name age:(NSInteger)age {
Person *person = [[Person alloc] init];
person.name = name;
person.age = age;
return person;
}
- (NSMutableArray<NSString *> *)nicks {
if (!_nicks) {
_nicks = [[NSMutableArray alloc] init];
}
return _nicks;
}
@end
@interface ViewController ()
@property (nonatomic, strong) Person *person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.person st_addObserver:self forKey:@"name" withBlock:^(id observedObject, NSString *observedKey, id oldValue, id newValue) {
NSLog(@"name = %@", newValue);
}];
[self.person st_addObserver:self forKey:@"nicks" withBlock:^(id observedObject, NSString *observedKey, id oldValue, id newValue) {
NSLog(@"nicks = %@", newValue);
NSLog(@"***********************");
}];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(personGrown)];
[self.view addGestureRecognizer:tap];
}
- (void)dealloc {
[self.person st_removeObserver:self forKey:nil];
}
- (void)personGrown {
NSLog(@"***********************");
self.person.age++;
NSArray *names = @[@"shuzt", @"laoniu", @"atai", @"sameny"];
self.person.name = names[self.person.age%4];
[[self.person mutableArrayValueForKey:@"nicks"] addObject:self.person.name];
if (self.person.age > 33) {
NSLog(@"remove observers");
[self.person st_removeObserver:self forKey:nil];
}
}
- (Person *)person {
if (!_person) {
_person = [Person personWithName:@"sameny" age:27];
}
return _person;
}
@end
打印结果如下:
2018-09-02 17:53:02.519160+0800 STKVOImplement[19134:6128949] ***********************
2018-09-02 17:53:02.519529+0800 STKVOImplement[19134:6129061] name = shuzt
2018-09-02 17:53:02.520006+0800 STKVOImplement[19134:6129061] nicks = (
shuzt
)
2018-09-02 17:53:02.520061+0800 STKVOImplement[19134:6129061] ***********************
2018-09-02 17:53:04.221709+0800 STKVOImplement[19134:6128949] ***********************
2018-09-02 17:53:04.222730+0800 STKVOImplement[19134:6129061] name = laoniu
2018-09-02 17:53:04.223088+0800 STKVOImplement[19134:6129061] nicks = (
shuzt,
laoniu
)
2018-09-02 17:53:04.223180+0800 STKVOImplement[19134:6129061] ***********************
2018-09-02 17:53:05.605007+0800 STKVOImplement[19134:6128949] ***********************
2018-09-02 17:53:05.605970+0800 STKVOImplement[19134:6129061] name = atai
2018-09-02 17:53:05.606307+0800 STKVOImplement[19134:6129060] nicks = (
shuzt,
laoniu,
atai
)
2018-09-02 17:53:05.606412+0800 STKVOImplement[19134:6129060] ***********************
至此,完成一个阶段,基于对象的KVO监听,基本实现,但是还无法监听基础数据类型的变化,欢迎有想法的前辈和伙伴们的指教,一起探讨。
网友评论