談一談你對KVO的理解?
A:添加響應(yīng)者,監(jiān)聽對象變化,當(dāng)對象改變時(shí)調(diào)用代理。
B:動(dòng)態(tài)創(chuàng)建NSKVONotifying_XX類,修改被監(jiān)聽對象isa指針指向,只要調(diào)用對象的set方法,
就會(huì)調(diào)用NSKVONotifying_XX的set方法。本質(zhì):判斷對象的set方法有沒有被調(diào)用
二、蘋果官方文檔描述
Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ..
以上文檔的基本內(nèi)容是:
“當(dāng)對象注冊觀察者時(shí),對象的ISA指針被修改,指向中間類,而不是原來真正的類...”,這是蘋果官方給出的文檔。聽到是不是很懵?沒關(guān)系,未了解之前,我和你一樣,看了這篇文章后你將和我一樣(小裝一下,勿噴)。
舉例說明:(監(jiān)聽一個(gè)Person類底層實(shí)現(xiàn))
2.1)、動(dòng)態(tài)創(chuàng)建NSKVONotifying_Person,NSKVONotifying_Person是Person子類,做KVO;
2.2)、修改當(dāng)前對象的isa指針->NSKVONotifying_Person;
2.3)、只要調(diào)用對象的set,就會(huì)調(diào)用NSKVONotifying_Person的set方法;
2.4)、重寫NSKVONotifying_Person的set方法,1[super set:] 2、通知觀察者,告訴屬性改變。
三、核心代碼
/**
?添加觀察者?(addObserver:?forKeyPath:?options:?context:)
?@param?observer?觀察者
?@param?keyPath?監(jiān)聽對象屬性
?@param?options?屬性配置
?@param?context?上下文
?*/
-?(void)addObserver:(NSObject?*)observer?forKeyPath:(NSString?*)keyPath
????????????options:(NSKeyValueObservingOptions)options
????????????context:(void*)context
{
????/*
?????*?options
?????*
?????*?NSKeyValueObservingOptionNew???????//?change字典包括改變后的值
?????*?NSKeyValueObservingOptionOld???????//?change字典包括改變前的值
?????*?NSKeyValueObservingOptionInitial???//?注冊后立刻觸發(fā)KVO通知
?????*?NSKeyValueObservingOptionPrior?????//?值改變前是否也要通知(通知兩次)
?????*/
}
/**
?監(jiān)聽按鈕狀態(tài)改變的方法
?@param?keyPath?改變的屬性
?@param?object?被監(jiān)聽對象
?@param?change?改變后的數(shù)據(jù)
?@param?context?注冊監(jiān)聽時(shí)context傳遞過來的值
?*/
-?(void)observeValueForKeyPath:(NSString?*)keyPath
??????????????????????ofObject:(id)object
????????????????????????change:(NSDictionary?*)change
???????????????????????context:(void*)context
{
}
到此為止,這是我以前了解的所有KVO了,就這么點(diǎn)東西,用起來嗖嗖嗖啊。但是,大神永遠(yuǎn)是最騷的。前幾天看到一個(gè)大神覺得系統(tǒng)的KVO不好用,自定義了一個(gè),居然還是15年寫的,我勒個(gè)槽槽槽,如何自己動(dòng)手實(shí)現(xiàn) KVO,就是這篇文章,讀了半天,終于弄明白了,總結(jié)總結(jié)吧,別白看了。
首先,懷著虛榮的心先了解一下,為什么要自定義?對啊,蘋果給的多好用,干嘛還要自定義去,脫褲子放屁(多此一舉嘛),別著急,聽聽big borther 怎么說。
四、進(jìn)階
4.1、KVO 缺陷
big brother原話:
"KVO 很強(qiáng)大,沒錯(cuò)。知道它內(nèi)部實(shí)現(xiàn),或許能幫助更好地使用它,或在它出錯(cuò)時(shí)更方便調(diào)試。但官方實(shí)現(xiàn)的 KVO 提供的 API 實(shí)在不怎么樣。
比如,你只能通過重寫 -observeValueForKeyPath:ofObject:change:context: 方法來獲得通知。想要提供自定義的 selector ,不行;想要傳一個(gè) block ,門都沒有。而且你還要處理父類的情況 - 父類同樣監(jiān)聽同一個(gè)對象的同一個(gè)屬性。但有時(shí)候,你不知道父類是不是對這個(gè)消息有興趣。雖然 context 這個(gè)參數(shù)就是干這個(gè)的,也可以解決這個(gè)問題 - 在 -addObserver:forKeyPath:options:context: 傳進(jìn)去一個(gè)父類不知道的 context。但總覺得框在這個(gè) API 的設(shè)計(jì)下,代碼寫的很別扭。至少至少,也應(yīng)該支持 block 吧。
有不少人都覺得官方 KVO 不好使的。Mike Ash 的 Key-Value Observing Done Right,以及獲得不少分享討論的 KVO Considered Harmful 都把 KVO 拿出來吊打了一番。所以在實(shí)際開發(fā)中 KVO 使用的情景并不多,更多時(shí)候還是用 Delegate 或 NotificationCenter。"
目的:自定義一個(gè)KVO
操作:創(chuàng)建 NSObject+KVO 類,封裝自定義API;
4.2、創(chuàng)建一個(gè)NSObject的Category,?.h文件中暴露兩個(gè)API,用于添加和刪除KVO。例:
#import//?宏定義
NSString?*constkPGKVOClassPrefix?=?@"PGKVOClassPrefix_";
NSString?*constkPGKVOAssociatedObservers?=?@"PGKVOAssociatedObservers";
/**?監(jiān)聽回調(diào)用block?*/
typedef?void(^PGObservingBlock)(id?observedObject,
????????????????????????????????NSString?*observedKey,
????????????????????????????????id?oldValue,?id?newValue);
@interfaceNSObject?(KVO)
/**?添加觀察者?*/
-?(void)PG_addObserver:(NSObject?*)observer
????????????????forKey:(NSString?*)key
?????????????withBlock:(PGObservingBlock)block;
/**?移除觀察者?*/
-?(void)PG_removeObserver:(NSObject?*)observer
???????????????????forKey:(NSString?*)key;
@end
.m內(nèi)部實(shí)踐
/*
?*?1、檢查對象的類有沒有相應(yīng)的?setter?方法。如果沒有拋出異常;
?*?2、檢查對象?isa?指向的類是不是一個(gè)?KVO?類。如果不是,新建一個(gè)繼承原來類的子類,并把?isa?指向這個(gè)新建的子類;
?*?3、檢查對象的?KVO?類重寫過沒有這個(gè)?setter?方法。如果沒有,添加重寫的?setter?方法;
?*?4、添加這個(gè)觀察者
?*/
-?(void)PG_addObserver:(NSObject?*)observer
????????????????forKey:(NSString?*)key
?????????????withBlock:(PGObservingBlock)block
{
????/*
?????一、?檢查對象的類有沒有相應(yīng)的setter方法,如果沒有拋出異常
????具體細(xì)節(jié):
????????1.1)、先通過?setterForGetter()?方法獲得相應(yīng)的?setter?的名字(SEL)。
????????1.2)、把key的首字母大寫;?前面加上set;?key就變成了setKey。
????????1.3)、再用class_getInstanceMethod去獲得setKey:的實(shí)現(xiàn)(Method),如果沒有,自然要拋出異常
?????*/
????SEL?setterSelector?=?NSSelectorFromString(setterForGetter(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;
????}
????/*
?????二、檢查對象isa指向的類是不是一個(gè)KVO類。如果不是,新建一個(gè)繼承原來類的子類,并把isa指向這個(gè)新建的子類
?????*/
????Class?clazz?=?object_getClass(self);
????NSString?*clazzName?=?NSStringFromClass(clazz);
????//?2.1)、先看類名有沒有我們定義的前綴。如果沒有,我們就去創(chuàng)建新的子類
????if(![clazzName?hasPrefix:kPGKVOClassPrefix])
????{
????????clazz?=?[self?makeKvoClassWithOriginalClassName:clazzName];
????????object_setClass(self,?clazz);
????}
????//?2.2)、到這里為止,?object的類已不是原類了,?而是KVO新建的類
????//?2.3)、例如官方的API:?Person?->?NSKVONotifying_Person()
????//?2.4)、kPGKVOClassPrefix是自己定義的一個(gè)宏,便于區(qū)分系統(tǒng)
????/*
?????三、重寫setter方法。新的setter在調(diào)用原setter方法后,通知每個(gè)觀察者(調(diào)用之前傳入的block)
?????*/
????if(![self?hasSelector:setterSelector])
????{
????????constchar?*types?=?method_getTypeEncoding(setterMethod);
????????class_addMethod(clazz,?setterSelector,?(IMP)kvo_setter,?types);
????}
????/*
?????四、把這個(gè)觀察的相關(guān)信息存在associatedObject里。
?????具體相關(guān):
????????觀察的相關(guān)信息(觀察者,被觀察的?key,?和傳入的?block?)封裝在?PGObservationInfo?類里。
?????*/
????PGObservationInfo?*info?=?[[PGObservationInfo?alloc]?initWithObserver:observer?Key:key?block:block];
????NSMutableArray?*observers?=?objc_getAssociatedObject(self,?(__bridge?constvoid*)(kPGKVOAssociatedObservers));
????if(!observers)
????{
????????observers?=?[NSMutableArray?array];
????????objc_setAssociatedObject(self,?(__bridge?constvoid*)(kPGKVOAssociatedObservers),?observers,?OBJC_ASSOCIATION_RETAIN_NONATOMIC);
????}
????[observers?addObject:info];
}
這就是自定義的核心代碼了,內(nèi)容注視都整理在里面,有幾個(gè)方法需要具體介紹一下:
4.3、核心方法解讀:
setterForGetter
getterForSetter
makeKvoClassWithOriginalClassName
kvo_class
PG_removeObserver
4.3.1、setterForGetter (name -> Name -> setName:)
/**?根據(jù)getter方法名獲得對應(yīng)的setter方法名?*/
staticNSString?*?setterForGetter(NSString?*getter)
{
????if(getter.length?<=?0)?{
????????returnnil;
????}
????//?upper?case?the?first?letter
????NSString?*firstLetter?=?[[getter?substringToIndex:1]?uppercaseString];
????NSString?*remainingLetters?=?[getter?substringFromIndex:1];
????//?add?'set'?at?the?begining?and?':'?at?the?end
????NSString?*setter?=?[NSString?stringWithFormat:@"set%@%@:",?firstLetter,?remainingLetters];
????returnsetter;
}
4.3.2、getterForSetter(setName: -> Name -> name)
/**?根據(jù)setter方法名獲得對應(yīng)的getter方法名?*/
staticNSString?*?getterForSetter(NSString?*setter)
{
????if(setter.length?<=0||?![setter?hasPrefix:@"set"]?||?![setter?hasSuffix:@":"])?{
????????returnnil;
????}
????//?remove?'set'?at?the?begining?and?':'?at?the?end
????NSRange?range?=?NSMakeRange(3,?setter.length?-?4);
????NSString?*key?=?[setter?substringWithRange:range];
????//?lower?case?the?first?letter
????NSString?*firstLetter?=?[[key?substringToIndex:1]?lowercaseString];
????key?=?[key?stringByReplacingCharactersInRange:NSMakeRange(0,?1)
???????????????????????????????????????withString:firstLetter];?
????returnkey;
}
4.3.3、makeKvoClassWithOriginalClassName
-?(Class)makeKvoClassWithOriginalClassName:(NSString?*)originalClazzName
{
????//?生成kPGKVOClassPrefix_class的類名
????NSString?*kvoClazzName?=?[kPGKVOClassPrefix?stringByAppendingString:originalClazzName];
????Class?clazz?=?NSClassFromString(kvoClazzName);
????//?如果kvo?class已經(jīng)被注冊過了,?則直接返回
????if(clazz)?{
????????returnclazz;
????}
????/*
?????*??如果kvo?class不存在,?則創(chuàng)建這個(gè)類
?????*??class?doesn't?exist?yet,?make?it
?????*/
????Class?originalClazz?=?object_getClass(self);
????Class?kvoClazz?=?objc_allocateClassPair(originalClazz,?kvoClazzName.UTF8String,?0);
????/*
?????*??修改kvo?class方法的實(shí)現(xiàn),?學(xué)習(xí)Apple的做法,?隱瞞這個(gè)kvo_class
?????*??grab?class?method's?signature?so?we?can?borrow?it
?????*/
????Method?clazzMethod?=?class_getInstanceMethod(originalClazz,?@selector(class));
????constchar?*types?=?method_getTypeEncoding(clazzMethod);
????class_addMethod(kvoClazz,?@selector(class),?(IMP)kvo_class,?types);
????//?注冊kvo_class
????objc_registerClassPair(kvoClazz);
????returnkvoClazz;
}
4.3.4、kvo_class
1
2
3
4
staticClass?kvo_class(id?self,?SEL?_cmd)
{
????returnclass_getSuperclass(object_getClass(self));
}
4.3.5、kvo_setter
#pragma?mark?-?Overridden?Methods
/**?重寫setter方法,?新方法在調(diào)用原方法后,?通知每個(gè)觀察者(調(diào)用傳入的block)?*/
staticvoidkvo_setter(id?self,?SEL?_cmd,?id?newValue)
{
????NSString?*setterName?=?NSStringFromSelector(_cmd);
????NSString?*getterName?=?getterForSetter(setterName);
????//?如果不存在getter方法
????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];
????//?調(diào)用原類的setter方法
????struct?objc_super?superclazz?=?{
????????.receiver?=?self,
????????.super_class?=?class_getSuperclass(object_getClass(self))
????};
????/*
?????*???這里需要做個(gè)類型強(qiáng)轉(zhuǎn),?否則會(huì)報(bào)too?many?argument的錯(cuò)誤
?????*???cast?our?pointer?so?the?compiler?won't?complain
?????*/
????void(*objc_msgSendSuperCasted)(void*,?SEL,?id)?=?(void*)objc_msgSendSuper;
????/*
?????*???call?super's?setter,?which?is?original?class's?setter?method
?????*/
????objc_msgSendSuperCasted(&superclazz,?_cmd,?newValue);
????/*
?????*??找出觀察者的數(shù)組,?調(diào)用對應(yīng)對象的callback
?????*??look?up?observers?and?call?the?blocks
?????*/
????NSMutableArray?*observers?=?objc_getAssociatedObject(self,?(__bridge?constvoid*)(kPGKVOAssociatedObservers));
?????//?遍歷數(shù)組
????for(PGObservationInfo?*eachinobservers)
????{
????????if([each.key?isEqualToString:getterName])
????????{
????????????//?gcd異步調(diào)用callback
????????????dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0),?^{
????????????????each.block(self,?getterName,?oldValue,?newValue);
????????????});
????????}
????}
}
4.3.6、PG_removeObserver
/**?移除觀察者?*/
-?(void)PG_removeObserver:(NSObject?*)observer?forKey:(NSString?*)key
{
????NSMutableArray*?observers?=?objc_getAssociatedObject(self,?(__bridge?constvoid*)(kPGKVOAssociatedObservers));
????PGObservationInfo?*infoToRemove;
????for(PGObservationInfo*?info?inobservers)
????{
????????if(info.observer?==?observer?&&?[info.key?isEqual:key])
????????{
????????????infoToRemove?=?info;
????????????break;
????????}
????}
????[observers?removeObject:infoToRemove];
}
4.3.7、調(diào)用
[self.message?PG_addObserver:self?forKey:NSStringFromSelector(@selector(text))?withBlock:^(id?observedObject,?NSString?*observedKey,?id?oldValue,?id?newValue)?{
}];