在iOS開發中,咱們能夠經過KVO機制來監聽某個對象的某個屬性的變化。git
用過KVO的同窗都應該知道,KVO的回調是以代理的形式實現的:在給某個對象添加觀察之後,須要在另一個地方實現回調代理方法。這種設計給人感受比較分散,所以忽然想試試用Block來實現KVO,將添加觀察的代碼和回調處理的代碼寫在一塊兒。在學習了ImplementKVO的實現之後,本身也寫了一個:SJKVOController程序員
只須要引入NSObject+SJKVOController.h
頭文件就可使用SJKVOController。 先看一下它的頭文件:github
#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能夠看出,這個小輪子:編程
下面來結合Demo講解一下如何使用這個小輪子:json
在點擊上面兩個按鈕中的任意一個,增長觀察:dom
一次性添加:async
- (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;
});
}];
}
複製代碼
添加之後,點擊最下面的按鈕來顯示全部的觀察信息:ui
- (IBAction)showAllObservingItems:(UIButton *)sender {
[self.model sj_listAllObservers];
}
複製代碼
輸出:this
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
複製代碼
##SJKVOError
這個小輪子仿照了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
---------------------------- 2018年7月17日更新 ----------------------------
注意注意!!!
筆者在近期開通了我的公衆號,主要分享編程,讀書筆記,思考類的文章。
由於公衆號天天發佈的消息數有限制,因此到目前爲止尚未將全部過去的精選文章都發布在公衆號上,後續會逐步發佈的。
並且由於各大博客平臺的各類限制,後面還會在公衆號上發佈一些短小精幹,以小見大的乾貨文章哦~
掃下方的公衆號二維碼並點擊關注,期待與您的共同成長~