在iOS开发中,我们可以通过KVO机制来监听某个对象的某个属性的变化。
用过KVO的同学都应该知道,KVO的回调是以代理的形式实现的:在给某个对象添加观察以后,需要在另外一个地方实现回调代理方法。这种设计给人感觉比较分散,因此突然想试试用Block来实现KVO,将添加观察的代码和回调处理的代码写在一起。在学习了ImplementKVO的实现以后,自己也写了一个:SJKVOController
只需要引入NSObject+SJKVOController.h
头文件就可以使用SJKVOController。
先看一下它的头文件:
#import <Foundation/Foundation.h> #import "SJKVOHeader.h" @interface NSObject (SJKVOController) //============== add observer ===============// - (void)sj_addObserver:(NSObject *)observer forKeys:(NSArray <NSString *>*)keys withBlock:(SJKVOBlock)block; - (void)sj_addObserver:(NSObject *)observer forKey:(NSString *)key withBlock:(SJKVOBlock)block; //============= remove observer =============// - (void)sj_removeObserver:(NSObject *)observer forKeys:(NSArray <NSString *>*)keys; - (void)sj_removeObserver:(NSObject *)observer forKey:(NSString *)key; - (void)sj_removeObserver:(NSObject *)observer; - (void)sj_removeAllObservers; //============= list observers ===============// - (void)sj_listAllObservers; @end
从上面的API可以看出,这个小轮子:
- 支持一次观察同一对象的多个属性。
- 可以一次只观察一个对象的一个属性。
- 可以移除对某个对象对多个属性的观察。
- 可以移除对某个对象对某个属性的观察。
- 可以移除某个观察自己的对象。
- 可以移除所有观察自己的对象。
- 打印出所有观察自己的对象的信息,包括对象本身,观察的属性,setter方法。
下面来结合Demo讲解一下如何使用这个小轮子:
在点击上面两个按钮中的任意一个,增加观察:
一次性添加:
- (IBAction)addObserversTogether:(UIButton *)sender { NSArray *keys = @[@"number",@"color"]; [self.model sj_addObserver:self forKeys:keys withBlock:^(id observedObject, NSString *key, id oldValue, id newValue) { if ([key isEqualToString:@"number"]) { dispatch_async(dispatch_get_main_queue(), ^{ self.numberLabel.text = [NSString stringWithFormat:@"%@",newValue]; }); }else if ([key isEqualToString:@"color"]){ dispatch_async(dispatch_get_main_queue(), ^{ self.numberLabel.backgroundColor = newValue; }); } }]; }
分两次添加:
- (IBAction)addObserverSeparatedly:(UIButton *)sender { [self.model sj_addObserver:self forKey:@"number" withBlock:^(id observedObject, NSString *key, id oldValue, id newValue) { dispatch_async(dispatch_get_main_queue(), ^{ self.numberLabel.text = [NSString stringWithFormat:@"%@",newValue]; }); }]; [self.model sj_addObserver:self forKey:@"color" withBlock:^(id observedObject, NSString *key, id oldValue, id newValue) { dispatch_async(dispatch_get_main_queue(), ^{ self.numberLabel.backgroundColor = newValue; }); }]; }
添加以后,点击最下面的按钮来显示所有的观察信息:
- (IBAction)showAllObservingItems:(UIButton *)sender { [self.model sj_listAllObservers]; }
输出:
SJKVOController[80499:4242749] SJKVOLog:==================== Start Listing All Observers: ==================== SJKVOController[80499:4242749] SJKVOLog:observer item:{observer: <ViewController: 0x7fa1577054f0> | key: color | setter: setColor:} SJKVOController[80499:4242749] SJKVOLog:observer item:{observer: <ViewController: 0x7fa1577054f0> | key: number | setter: setNumber:}
在这里我重写了description方法,打印出了每个观察的对象和key,以及setter方法。
现在点击更新按钮,则会更新model的number和color属性,从而触发KVO:
- (IBAction)updateNumber:(UIButton *)sender { //trigger KVO : number NSInteger newNumber = arc4random() % 100; self.model.number = [NSNumber numberWithInteger:newNumber]; //trigger KVO : color NSArray *colors = @[[UIColor redColor],[UIColor yellowColor],[UIColor blueColor],[UIColor greenColor]]; NSInteger colorIndex = arc4random() % 3; self.model.color = colors[colorIndex]; }
我们可以看到中间的Label上面显示的数字和背景色都在变化,成功实现了KVO:
现在我们移除观察,点击remove按钮
- (IBAction)removeAllObservingItems:(UIButton *)sender { [self.model sj_removeAllObservers]; }
在移除了所有的观察者以后,则会打印出:
SJKVOController[80499:4242749] SJKVOLog:Removed all obserbing objects of object:<Model: 0x60000003b700>
而且如果在这个时候打印观察者list,则会输出:
SJKVOController[80499:4242749] SJKVOLog:There is no observers obserbing object:<Model: 0x60000003b700>
需要注意的是,这里的移除可以有多种选择:可以移某个对象的某个key,也可以移除某个对象的几个keys,为了验证,我们可以结合list方法来验证一下移除是否成功:
- (IBAction)removeAllObservingItems:(UIButton *)sender { [self.model sj_removeObserver:self forKey:@"number"]; }
在移除以后,我们调用list方法,输出:
SJKVOController[80850:4278383] SJKVOLog:==================== Start Listing All Observers: ==================== SJKVOController[80850:4278383] SJKVOLog:observer item:{observer: <ViewController: 0x7ffeec408560> | key: color | setter: setColor:}
现在只有color属性被观察了。看一下实际的效果:
我们可以看到,现在只有color在变,而数字没有变化了,验证此移除方法正确。
- (IBAction)removeAllObservingItems:(UIButton *)sender { [self.model sj_removeObserver:self forKeys:@[@"number",@"color"]]; }
在移除以后,我们调用list方法,输出:
SJKVOController[80901:4283311] SJKVOLog:There is no observers obserbing object:<Model: 0x600000220fa0>
现在color和number属性都不被观察了。看一下实际的效果:
我们可以看到,现在color和number都不变了,验证此移除方法正确。
OK,现在知道了怎么用SJKVOController,我下面给大家看一下代码:
先大致讲解一下SJKVOController的实现思路:
再来看一下这个小轮子的几个类:
下面开始一个一个来讲解每个类的源码:
再看一下头文件:
#import <Foundation/Foundation.h> #import "SJKVOHeader.h" @interface NSObject (SJKVOController) //============== add observer ===============// - (void)sj_addObserver:(NSObject *)observer forKeys:(NSArray <NSString *>*)keys withBlock:(SJKVOBlock)block; - (void)sj_addObserver:(NSObject *)observer forKey:(NSString *)key withBlock:(SJKVOBlock)block; //============= remove observer =============// - (void)sj_removeObserver:(NSObject *)observer forKeys:(NSArray <NSString *>*)keys; - (void)sj_removeObserver:(NSObject *)observer forKey:(NSString *)key; - (void)sj_removeObserver:(NSObject *)observer; - (void)sj_removeAllObservers; //============= list observers ===============// - (void)sj_listAllObservers; @end
每个方法的意思相信读者已经能看懂了,现在讲一下具体的实现。从sj_addObserver:forKey withBlock:
开始:
除去一些错误的判断,该方法作了下面几件事情:
SEL setterSelector = NSSelectorFromString([SJKVOTool setterFromGetter:key]); Method setterMethod = [SJKVOTool objc_methodFromClass:[self class] selector:setterSelector]; //error: no corresponding setter mothod if (!setterMethod) { SJLog(@"%@",[SJKVOError errorNoMatchingSetterForKey:key]); return; }
//get original class(current class,may be KVO class) NSString *originalClassName = NSStringFromClass(OriginalClass); //如果当前的类是带有KVO前缀的类(也就是已经被观察到类),则需要将KVO前缀的类删除,并讲 if ([originalClassName hasPrefix:SJKVOClassPrefix]) { //now,the OriginalClass is KVO class, we should destroy it and make new one Class CurrentKVOClass = OriginalClass; object_setClass(self, class_getSuperclass(OriginalClass)); objc_disposeClassPair(CurrentKVOClass); originalClassName = [originalClassName substringFromIndex:(SJKVOClassPrefix.length)]; }
//create a KVO class Class KVOClass = [self createKVOClassFromOriginalClassName:originalClassName]; //swizzle isa from self to KVO class object_setClass(self, KVOClass);
看一下如何新建一个新的类:
- (Class)createKVOClassFromOriginalClassName:(NSString *)originalClassName { NSString *kvoClassName = [SJKVOClassPrefix stringByAppendingString:originalClassName]; Class KVOClass = NSClassFromString(kvoClassName); // KVO class already exists if (KVOClass) { return KVOClass; } // if there is no KVO class, then create one KVOClass = objc_allocateClassPair(OriginalClass, kvoClassName.UTF8String, 0);//OriginalClass is super class // pretending to be the original class:return the super class in class method Method clazzMethod = class_getInstanceMethod(OriginalClass, @selector(class)); class_addMethod(KVOClass, @selector(class), (IMP)return_original_class, method_getTypeEncoding(clazzMethod)); // finally, register this new KVO class objc_registerClassPair(KVOClass); return KVOClass; }
//if we already have some history observer items, we should add them into new KVO class NSMutableSet* observers = objc_getAssociatedObject(self, &SJKVOObservers); if (observers.count > 0) { NSMutableSet *newObservers = [[NSMutableSet alloc] initWithCapacity:5]; objc_setAssociatedObject(self, &SJKVOObservers, newObservers, OBJC_ASSOCIATION_RETAIN_NONATOMIC); for (SJKVOObserverItem *item in observers) { [self KVOConfigurationWithObserver:item.observer key:item.key block:item.block kvoClass:KVOClass setterSelector:item.setterSelector setterMethod:setterMethod]; } }
看一下如何保存观察项的:
- (void)KVOConfigurationWithObserver:(NSObject *)observer key:(NSString *)key block:(SJKVOBlock)block kvoClass:(Class)kvoClass setterSelector:(SEL)setterSelector setterMethod:(Method)setterMethod { //add setter method in KVO Class if(![SJKVOTool detectClass:OriginalClass hasSelector:setterSelector]){ class_addMethod(kvoClass, setterSelector, (IMP)kvo_setter_implementation, method_getTypeEncoding(setterMethod)); } //add item of this observer&&key pair [self addObserverItem:observer key:key setterSelector:setterSelector setterMethod:setterMethod block:block]; }
这里首先给KVO类增加了setter方法:
//implementation of KVO setter method void kvo_setter_implementation(id self, SEL _cmd, id newValue) { NSString *setterName = NSStringFromSelector(_cmd); NSString *getterName = [SJKVOTool getterFromSetter:setterName]; if (!getterName) { SJLog(@"%@",[SJKVOError errorTransferSetterToGetterFaildedWithSetterName:setterName]); return; } // create a super class of a specific instance Class superclass = class_getSuperclass(OriginalClass); struct objc_super superclass_to_call = { .super_class = superclass, //super class .receiver = self, //insatance of this class }; // cast method pointer void (*objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper; // call super's setter, the supper is the original class objc_msgSendSuperCasted(&superclass_to_call, _cmd, newValue); // look up observers and call the blocks NSMutableSet *observers = objc_getAssociatedObject(self,&SJKVOObservers); if (observers.count <= 0) { SJLog(@"%@",[SJKVOError errorNoObserverOfObject:self]); return; } //get the old value id oldValue = [self valueForKey:getterName]; for (SJKVOObserverItem *item in observers) { if ([item.key isEqualToString:getterName]) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //call block item.block(self, getterName, oldValue, newValue); }); } } }
然后实例化对应的观察项:
- (void)addObserverItem:(NSObject *)observer key:(NSString *)key setterSelector:(SEL)setterSelector setterMethod:(Method)setterMethod block:(SJKVOBlock)block { NSMutableSet *observers = objc_getAssociatedObject(self, &SJKVOObservers); if (!observers) { observers = [[NSMutableSet alloc] initWithCapacity:10]; objc_setAssociatedObject(self, &SJKVOObservers, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } SJKVOObserverItem *item = [[SJKVOObserverItem alloc] initWithObserver:observer Key:key setterSelector:setterSelector setterMethod:setterMethod block:block]; if (item) { [observers addObject:item]; } }
/ /ignore same observer and key:if the observer and key are same with saved observerItem,we should not add them one more time BOOL findSameObserverAndKey = NO; if (observers.count>0) { for (SJKVOObserverItem *item in observers) { if ( (item.observer == observer) && [item.key isEqualToString:key]) { findSameObserverAndKey = YES; } } } if (!findSameObserverAndKey) { [self KVOConfigurationWithObserver:observer key:key block:block kvoClass:KVOClass setterSelector:setterSelector setterMethod:setterMethod]; }
而一次性添加多个key的方法,也只是调用多次一次性添加单个key的方法罢了:
- (void)sj_addObserver:(NSObject *)observer forKeys:(NSArray <NSString *>*)keys withBlock:(SJKVOBlock)block { //error: keys array is nil or no elements if (keys.count == 0) { SJLog(@"%@",[SJKVOError errorInvalidInputObservingKeys]); return; } //one key corresponding to one specific item, not the observer [keys enumerateObjectsUsingBlock:^(NSString * key, NSUInteger idx, BOOL * _Nonnull stop) { [self sj_addObserver:observer forKey:key withBlock:block]; }]; }
关于移除观察的实现,只是在观察项set里面找出封装了对应的观察对象和key的观察项就可以了:
- (void)sj_removeObserver:(NSObject *)observer forKey:(NSString *)key { NSMutableSet* observers = objc_getAssociatedObject(self, &SJKVOObservers); if (observers.count > 0) { SJKVOObserverItem *removingItem = nil; for (SJKVOObserverItem* item in observers) { if (item.observer == observer && [item.key isEqualToString:key]) { removingItem = item; break; } } if (removingItem) { [observers removeObject:removingItem]; } } }
再看一下移除所有观察者:
- (void)sj_removeAllObservers { NSMutableSet* observers = objc_getAssociatedObject(self, &SJKVOObservers); if (observers.count > 0) { [observers removeAllObjects]; SJLog(@"SJKVOLog:Removed all obserbing objects of object:%@",self); }else{ SJLog(@"SJKVOLog:There is no observers obserbing object:%@",self); } }
这个类负责封装每一个观察项的信息,包括:
需要注意的是:
在这个小轮子里,对于同一个对象可以观察不同的key的情况,是将这两个key区分开来的,是属于不同的观察项。所以应该用不同的SJKVOObserverItem
实例来封装。
#import <Foundation/Foundation.h> #import <objc/runtime.h> typedef void(^SJKVOBlock)(id observedObject, NSString *key, id oldValue, id newValue); @interface SJKVOObserverItem : NSObject @property (nonatomic, strong) NSObject *observer; @property (nonatomic, copy) NSString *key; @property (nonatomic, assign) SEL setterSelector; @property (nonatomic, assign) Method setterMethod; @property (nonatomic, copy) SJKVOBlock block; - (instancetype)initWithObserver:(NSObject *)observer Key:(NSString *)key setterSelector:(SEL)setterSelector setterMethod:(Method)setterMethod block:(SJKVOBlock)block; @end
这个类负责setter方法与getter方法相互转换,以及和运行时相关的操作,服务于SJKVOController
。看一下它的头文件:
#import <Foundation/Foundation.h> #import <objc/runtime.h> #import <objc/message.h> @interface SJKVOTool : NSObject //setter <-> getter + (NSString *)getterFromSetter:(NSString *)setter; + (NSString *)setterFromGetter:(NSString *)getter; //get method from a class by a specific selector + (Method)objc_methodFromClass:(Class)cls selector:(SEL)selector; //check a class has a specific selector or not + (BOOL)detectClass:(Class)cls hasSelector:(SEL)selector; @end
这个小轮子仿照了JSONModel的错误管理方式,用单独的一个类SJKVOError
来返回各种错误:
#import <Foundation/Foundation.h> typedef enum : NSUInteger { SJKVOErrorTypeNoObervingObject, SJKVOErrorTypeNoObervingKey, SJKVOErrorTypeNoObserverOfObject, SJKVOErrorTypeNoMatchingSetterForKey, SJKVOErrorTypeTransferSetterToGetterFailded, SJKVOErrorTypeInvalidInputObservingKeys, } SJKVOErrorTypes; @interface SJKVOError : NSError + (id)errorNoObervingObject; + (id)errorNoObervingKey; + (id)errorNoMatchingSetterForKey:(NSString *)key; + (id)errorTransferSetterToGetterFaildedWithSetterName:(NSString *)setterName; + (id)errorNoObserverOfObject:(id)object; + (id)errorInvalidInputObservingKeys; @end
OK,这样就介绍完了,希望各位同学可以积极指正~
本篇已同步到个人博客:使用Block实现KVO
本文已在版权印备案,如需转载请访问版权印。48422928