GNUstep KVC/KVO探索(一):KVC的內(nèi)部實(shí)現(xiàn)
GNUstep KVC/KVO探索(二):KVO的內(nèi)部實(shí)現(xiàn)
概述
KVO全稱(chēng)KeyValueObserving鹊汛,是蘋(píng)果提供的一套事件通知機(jī)制纷责。允許對(duì)象監(jiān)聽(tīng)另一個(gè)對(duì)象特定屬性的改變枪汪,并在改變時(shí)接收到事件篇亭。由于KVO的實(shí)現(xiàn)機(jī)制敞咧,所以對(duì)屬性才會(huì)發(fā)生作用趾代,一般繼承自NSObject的對(duì)象都默認(rèn)支持KVO。
KVO和NSNotificationCenter都是iOS中觀察者模式的一種實(shí)現(xiàn)调鲸。區(qū)別在于,相對(duì)于被觀察者和觀察者之間的關(guān)系挽荡,KVO是一對(duì)一的藐石,而NSNotificationCenter是一對(duì)多的。KVO對(duì)被監(jiān)聽(tīng)對(duì)象無(wú)侵入性定拟,不需要修改其內(nèi)部代碼即可實(shí)現(xiàn)監(jiān)聽(tīng)于微。
KVO可以監(jiān)聽(tīng)單個(gè)屬性的變化,也可以監(jiān)聽(tīng)集合對(duì)象的變化青自。通過(guò)KVC的mutableArrayValueForKey:等方法獲得代理對(duì)象株依,當(dāng)代理對(duì)象的內(nèi)部對(duì)象發(fā)生改變時(shí),會(huì)回調(diào)KVO監(jiān)聽(tīng)的方法延窜。集合對(duì)象包含NSArray和NSSet恋腕。
基礎(chǔ)使用
使用KVO分為三個(gè)步驟:
通過(guò)addObserver:forKeyPath:options:context:方法注冊(cè)觀察者,觀察者可以接收keyPath屬性的變化事件逆瑞。
在觀察者中實(shí)現(xiàn)observeValueForKeyPath:ofObject:change:context:方法荠藤,當(dāng)keyPath屬性發(fā)生改變后,KVO會(huì)回調(diào)這個(gè)方法來(lái)通知觀察者获高。
當(dāng)觀察者不需要監(jiān)聽(tīng)時(shí)哈肖,可以調(diào)用removeObserver:forKeyPath:方法將KVO移除。需要注意的是念秧,調(diào)用removeObserver需要在觀察者消失之前淤井,否則會(huì)導(dǎo)致Crash。
KVO的實(shí)現(xiàn)
在分析KVO的內(nèi)部實(shí)現(xiàn)之前出爹,先來(lái)分析一下KVO的存儲(chǔ)結(jié)構(gòu)庄吼,主要用到了以下幾個(gè)類(lèi):
GSKVOInfo
GSKVOPathInfo
GSKVOObservation
GSKVOInfo
KVO是基于NSObject類(lèi)別實(shí)現(xiàn)的非正式協(xié)議,所以所有繼承于NSObject的類(lèi)都可以使用KVO严就,其中可以通過(guò)- (void*) observationInfo
方法獲取對(duì)象相關(guān)聯(lián)的所有KVO信息总寻,而返回值就是GSKVOInfo
,源碼如下
@interface GSKVOInfo : NSObject
{
NSObject *instance; // Not retained.
GSLazyRecursiveLock *iLock;
NSMapTable *paths;
}
- 它保存了一個(gè)對(duì)象的實(shí)例梢为,重點(diǎn) Not retained 由于沒(méi)有持有渐行,也不是weak柒室,所以當(dāng)釋放之后,在調(diào)用會(huì)崩潰危队,需要在對(duì)象銷(xiāo)毀前宙枷,移除所有觀察者
-
paths
用于保存keyPath
到GSKVOPathInfo
的映射:
GSKVOPathInfo
@interface GSKVOPathInfo : NSObject
{
@public
unsigned recursion;
unsigned allOptions;
NSMutableArray *observations;
NSMutableDictionary *change;
}
它保存了一個(gè)keypath對(duì)應(yīng)的所有觀察者
observations
保存了所有的觀察者(GSKVOObservation
類(lèi)型)
allOptions
保存了觀察者的options
集合
change
保存了KVO觸發(fā)要傳遞的內(nèi)容
GSKVOObservation
@interface GSKVOObservation : NSObject
{
@public
NSObject *observer; // Not retained (zeroing weak pointer)
void *context;
int options;
}
@end
它保存了單個(gè)觀察的所有信息
-
observer
保存觀察者 注意這里也是 Not retained -
context options
都是添加觀察者時(shí)傳入的參數(shù)
上面幾個(gè)類(lèi)都是用來(lái)存儲(chǔ)KVO信息,而KVO是如何實(shí)現(xiàn)屬性變化通知的呢蕴忆?
KVO是通過(guò)isa-swizzling技術(shù)實(shí)現(xiàn)的(這句話是整個(gè)KVO實(shí)現(xiàn)的重點(diǎn))颤芬。在運(yùn)行時(shí)根據(jù)原類(lèi)創(chuàng)建一個(gè)中間類(lèi),這個(gè)中間類(lèi)是原類(lèi)的子類(lèi)套鹅,并動(dòng)態(tài)修改當(dāng)前對(duì)象的isa指向中間類(lèi)站蝠。并且將class方法重寫(xiě),返回原類(lèi)的Class卓鹿。所以蘋(píng)果建議在開(kāi)發(fā)中不應(yīng)該依賴(lài)isa指針菱魔,而是通過(guò)class實(shí)例方法來(lái)獲取對(duì)象類(lèi)型。
要實(shí)現(xiàn)isa-swizzling技術(shù)吟孙,主要通過(guò)以下幾個(gè)類(lèi)實(shí)現(xiàn):
GSKVOReplacement
GSKVOBase
GSKVOSetter
GSKVOReplacement
@interface GSKVOReplacement : NSObject
{
Class original; /* The original class */
Class replacement; /* The replacement class */
NSMutableSet *keys; /* The observed setter keys */
}
- (id) initWithClass: (Class)aClass;
- (void) overrideSetterFor: (NSString*)aKey;
- (Class) replacement;
@end
// 創(chuàng)建
- (id) initWithClass: (Class)aClass
{
NSValue *template;
NSString *superName;
NSString *name;
original = aClass;
/*
* Create subclass of the original, and override some methods
* with implementations from our abstract base class.
*/
superName = NSStringFromClass(original); // original == Temp
name = [@"GSKVO" stringByAppendingString: superName]; // name = GSKVOTemp
template = GSObjCMakeClass(name, superName, nil); // template = GSKVOTemp
GSObjCAddClasses([NSArray arrayWithObject: template]);
replacement = NSClassFromString(name);
GSObjCAddClassBehavior(replacement, baseClass);
/* Create the set of setter methods overridden.
*/
keys = [NSMutableSet new];
return self;
}
- 這個(gè)類(lèi)保存了被觀察對(duì)象原始類(lèi)信息
original
- 創(chuàng)建一個(gè)原始類(lèi)的子類(lèi) 命名為
GSKVO<原類(lèi)名>
澜倦,iOS系統(tǒng)使用命名規(guī)則為NSKVONotifying_原類(lèi)名
- 拷貝
GSKVOBase
類(lèi)中的方法到新類(lèi)中 - 后續(xù)會(huì)通過(guò)
object_setClass
, 將被觀察對(duì)象的isa
指向這個(gè)新類(lèi),也就是isa-swizzling
技術(shù)杰妓,而isa
保存的是類(lèi)的信息藻治,也就是說(shuō)被觀察者對(duì)象就變成了新類(lèi)的實(shí)例,這個(gè)新類(lèi)用于實(shí)現(xiàn)KVO通知機(jī)制
GSKVOBase
這個(gè)類(lèi)默認(rèn)提供了幾個(gè)方法巷挥,都是對(duì)NSObject方法的重寫(xiě)栋艳,而從上面得知,這些方法都要拷貝到新創(chuàng)建的替換類(lèi)中句各。也就是被觀察者會(huì)擁有這幾個(gè)方法的實(shí)現(xiàn)
- (void) dealloc
{
// Turn off KVO for self ... then call the real dealloc implementation.
[self setObservationInfo: nil];
object_setClass(self, [self class]);
[self dealloc];
GSNOSUPERDEALLOC;
}
對(duì)象釋放后吸占,移除KVO數(shù)據(jù),將對(duì)象重新指向原始類(lèi)
- (Class) class
{
return class_getSuperclass(object_getClass(self));
}
此方法用來(lái)隱藏替換類(lèi)信息凿宾,應(yīng)用層獲取類(lèi)的信息矾屯,仍然是原始類(lèi)的信息. 所以蘋(píng)果建議在開(kāi)發(fā)中不應(yīng)該依賴(lài)isa指針,而是通過(guò)class實(shí)例方法來(lái)獲取對(duì)象類(lèi)型初厚。
- (Class) superclass
{
return class_getSuperclass(class_getSuperclass(object_getClass(self)));
}
此方法和class
方法原理相同
- (void) setValue: (id)anObject forKey: (NSString*)aKey
{
Class c = [self class];
void (*imp)(id,SEL,id,id);
imp = (void (*)(id,SEL,id,id))[c instanceMethodForSelector: _cmd];
if ([[self class] automaticallyNotifiesObserversForKey: aKey])
{
[self willChangeValueForKey: aKey];
imp(self,_cmd,anObject,aKey);
[self didChangeValueForKey: aKey];
}
else
{
imp(self,_cmd,anObject,aKey);
}
}
這個(gè)方法是屬于KVC中的件蚕,重寫(xiě)這個(gè)方法,實(shí)現(xiàn)在原始類(lèi)KVC調(diào)用前后添加[self willChangeValueForKey: aKey]
和[self didChangeValueForKey: aKey]
,而這兩個(gè)方法是觸發(fā)KVO通知的關(guān)鍵产禾。
所以說(shuō)KVO是基于KVC的排作,而KVC正是KVO觸發(fā)的入口。
GSKVOBase
@interface GSKVOSetter : NSObject
- (void) setter: (void*)val;
- (void) setterChar: (unsigned char)val;
- (void) setterDouble: (double)val;
- (void) setterFloat: (float)val;
- (void) setterInt: (unsigned int)val;
- (void) setterLong: (unsigned long)val;
#ifdef _C_LNG_LNG
- (void) setterLongLong: (unsigned long long)val;
#endif
- (void) setterShort: (unsigned short)val;
- (void) setterRange: (NSRange)val;
- (void) setterPoint: (NSPoint)val;
- (void) setterSize: (NSSize)val;
- (void) setterRect: (NSRect)rect;
@end
這個(gè)類(lèi)和上面重寫(xiě)KVC方法原理相同亚情,將來(lái)會(huì)替換被觀察者keypath
的setter
方法實(shí)現(xiàn)妄痪。會(huì)在原始setter
方法前后添加[self willChangeValueForKey: aKey]
和[self didChangeValueForKey: aKey]
小結(jié)
那么到這里,就對(duì)KVO的實(shí)現(xiàn)有個(gè)大致的了解楞件,通過(guò)isa-swizzling
技術(shù), 替換被觀察的類(lèi)信息衫生,并且hook被觀察keyPath setter
方法裳瘪,在原始方法調(diào)用前后添加[self willChangeValueForKey: aKey]
和[self didChangeValueForKey: aKey]
,從而達(dá)到監(jiān)聽(tīng)屬性變化的功能
源碼實(shí)現(xiàn)
接下來(lái)從源碼中查看KVO的所有流程
- (void) addObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext
{
GSKVOInfo *info;
GSKVOReplacement *r;
NSKeyValueObservationForwarder *forwarder;
NSRange dot;
setup();
[kvoLock lock];
// Use the original class
r = replacementForClass([self class]);
/*
* Get the existing observation information, creating it (and changing
* the receiver to start key-value-observing by switching its class)
* if necessary.
*/
info = (GSKVOInfo*)[self observationInfo];
if (info == nil)
{
info = [[GSKVOInfo alloc] initWithInstance: self];
[self setObservationInfo: info];
object_setClass(self, [r replacement]);
}
/*
* Now add the observer.
*/
dot = [aPath rangeOfString:@"."];
if (dot.location != NSNotFound)
{
forwarder = [[NSKeyValueObservationForwarder alloc]
initWithKeyPath: aPath
ofObject: self
withTarget: anObserver
context: aContext];
[info addObserver: anObserver
forKeyPath: aPath
options: options
context: forwarder];
}
else
{
[r overrideSetterFor: aPath];
[info addObserver: anObserver
forKeyPath: aPath
options: options
context: aContext];
}
[kvoLock unlock];
}
KVO的入口罪针,添加觀察者方法彭羹,主要做了以下幾件事
-
replacementForClass
,創(chuàng)建替換類(lèi)泪酱,并且加入到全局classTable
中派殷,方便以后使用 - 獲取對(duì)象的監(jiān)聽(tīng)者數(shù)據(jù)
GSKVOInfo
,如果沒(méi)有就創(chuàng)建新的 - 接下來(lái)分為兩種情況
1?? 如果利用了點(diǎn)語(yǔ)法(self.keyPath.keyPath), 會(huì)利用NSKeyValueObservationForwarder
遞歸創(chuàng)建子對(duì)象監(jiān)聽(tīng)墓阀,子對(duì)象在將監(jiān)聽(tīng)到的變化轉(zhuǎn)發(fā)到上層愈腾,以后再具體分析
2?? 默認(rèn)情況(keyPath)直接監(jiān)聽(tīng)對(duì)象的某個(gè)屬性,則會(huì)調(diào)用overrideSetterFor
方法岂津,hook屬性的setter
方法,將setter
方法的實(shí)現(xiàn)替換為相應(yīng)參數(shù)類(lèi)型的GSKVOSetter
中的方法實(shí)現(xiàn) - 然后調(diào)用
[info addObserver: anObserver forKeyPath: aPath options: options context: aContext];
方法悦即,將新的監(jiān)聽(tīng)保存起來(lái)吮成。
GSKVOInfo 中的添加方法
- (void) addObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext
{
GSKVOPathInfo *pathInfo;
GSKVOObservation *observation;
unsigned count;
if ([anObserver respondsToSelector:
@selector(observeValueForKeyPath:ofObject:change:context:)] == NO)
{
return;
}
[iLock lock];
pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
if (pathInfo == nil)
{
pathInfo = [GSKVOPathInfo new];
// use immutable object for map key
aPath = [aPath copy];
NSMapInsert(paths, (void*)aPath, (void*)pathInfo);
[pathInfo release];
[aPath release];
}
observation = nil;
pathInfo->allOptions = 0;
count = [pathInfo->observations count];
while (count-- > 0)
{
GSKVOObservation *o;
o = [pathInfo->observations objectAtIndex: count];
if (o->observer == anObserver)
{
o->context = aContext;
o->options = options;
observation = o;
}
pathInfo->allOptions |= o->options;
}
if (observation == nil)
{
observation = [GSKVOObservation new];
GSAssignZeroingWeakPointer((void**)&observation->observer,
(void*)anObserver);
observation->context = aContext;
observation->options = options;
[pathInfo->observations addObject: observation];
[observation release];
pathInfo->allOptions |= options;
}
if (options & NSKeyValueObservingOptionInitial)
{
/* If the NSKeyValueObservingOptionInitial option is set,
* we must send an immediate notification containing the
* existing value in the NSKeyValueChangeNewKey
*/
[pathInfo->change setObject: [NSNumber numberWithInt: 1]
forKey: NSKeyValueChangeKindKey];
if (options & NSKeyValueObservingOptionNew)
{
id value;
value = [instance valueForKeyPath: aPath];
if (value == nil)
{
value = null;
}
[pathInfo->change setObject: value
forKey: NSKeyValueChangeNewKey];
}
[anObserver observeValueForKeyPath: aPath
ofObject: instance
change: pathInfo->change
context: aContext];
}
[iLock unlock];
}
此方法主要是保存觀察者信息
- 查詢(xún)相應(yīng)的
GSKVOPathInfo
--GSKVOObservation
如果有就更新,如果沒(méi)有就創(chuàng)建新的并保存 - 如果
options
中包含NSKeyValueObservingOptionInitial
辜梳,則立馬調(diào)用[anObserver observeValueForKeyPath: aPath ofObject: instance change: pathInfo->change context: aContext];
發(fā)送消息給觀察者
If the NSKeyValueObservingOptionInitial option is set, we must send an immediate notification containing the existing value in the NSKeyValueChangeNewKey
- 其中獲取當(dāng)前值通過(guò)KVC中的
[instance valueForKeyPath: aPath];
獲取
willChangeValueForKey
didChangeValueForKey
這兩個(gè)方法分別添加在setter
和KVC 賦值
前后粱甫,用來(lái)保存屬性值的變化,以及發(fā)送消息給觀察者
-
willChangeValueForKey
:
主要記錄屬性值的oldValue
保存到pathInfo->change
中作瞄,如果options
包含NSKeyValueObservingOptionPrior
茶宵,則會(huì)遍歷所有觀察者,立馬發(fā)送消息給觀察者
NSKeyValueObservingOptionPrior
表示屬性值修改前后都會(huì)收到通知 -
didChangeValueForKey
根據(jù)options
保存屬性的新舊值宗挥,遍歷所有的觀察者乌庶,發(fā)送消息
移除觀察者
/*
* removes the observer
*/
- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
GSKVOPathInfo *pathInfo;
[iLock lock];
pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
if (pathInfo != nil)
{
unsigned count = [pathInfo->observations count];
pathInfo->allOptions = 0;
while (count-- > 0)
{
GSKVOObservation *o;
o = [pathInfo->observations objectAtIndex: count];
if (o->observer == anObserver || o->observer == nil)
{
[pathInfo->observations removeObjectAtIndex: count];
if ([pathInfo->observations count] == 0)
{
NSMapRemove(paths, (void*)aPath);
}
}
else
{
pathInfo->allOptions |= o->options;
}
}
}
[iLock unlock];
}
此方法主要用來(lái)移除相應(yīng)keyPath的觀察者,方法實(shí)現(xiàn)很簡(jiǎn)單契耿,根據(jù)參數(shù)傳入的anObserver
和aPath
在前面介紹的數(shù)據(jù)結(jié)構(gòu)中查詢(xún)并移除
總結(jié)
到這里KVO的大致流程就分析完了
- 主要用了
isa-swizzling
,修改了觀察者的類(lèi)信息瞒大,并且hooksetter
方法,當(dāng)setter
方法調(diào)用時(shí)發(fā)送消息給所有觀察者 - 由上面源碼可以看出對(duì)觀察者搪桂、被觀察者的引用都是
Not Retain
, 所以對(duì)象釋放前一定要移除觀察者透敌。 - 消息的發(fā)送主要由
[self willChangeValueForKey: key]
,[self didChangeValueForKey: key]
觸發(fā),并且必須成對(duì)出現(xiàn)踢械,automaticallyNotifiesObserversForKey
方法用來(lái)控制酗电,是否要主要添加上述的兩個(gè)方法,默認(rèn)返回值為YES
内列,如果返回NO
則不會(huì)自動(dòng)添加撵术,也就是說(shuō)setter
的調(diào)用以及KVC修改都不會(huì)觸發(fā)通知 -
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key
此方法用來(lái)設(shè)置依賴(lài)關(guān)系,有時(shí)候需要某屬性值隨著同一對(duì)象的其他屬性的改變而改變话瞧『苫纾可以通過(guò)事先將這樣的依賴(lài)關(guān)系在類(lèi)中注冊(cè)退渗,那么即便屬性值間接地發(fā)生了改變,也會(huì)發(fā)送通知消息蕴纳,被觀察者類(lèi)重寫(xiě)返回和key
依賴(lài)的所有key
集合
內(nèi)部實(shí)現(xiàn)也比較簡(jiǎn)單会油,將所有依賴(lài)關(guān)系存儲(chǔ)在全局的dependentKeyTable
中,然后hook了所有依賴(lài)的key
的setter
方法古毛,當(dāng)[self willChangeValueForKey: key]
,[self didChangeValueForKey: key]
調(diào)用時(shí)會(huì)查找所有的依賴(lài)關(guān)系翻翩,然后發(fā)送消息 - KVO內(nèi)部多次用到了KVC
1?? 重寫(xiě)setValue:forKey
2?? 使用valueForKey --- valueForKeyPath
獲取屬性的值,尤其是在使用點(diǎn)語(yǔ)法的時(shí)候稻薇,只有valueForKeyPath
可以獲得深層次的屬性值嫂冻。
所以KVO是基于KVC而實(shí)現(xiàn)的。
NSKeyValueObservationForwarder
@interface NSKeyValueObservationForwarder : NSObject
{
id target;
NSKeyValueObservationForwarder *child;
void *contextToForward;
id observedObjectForUpdate;
NSString *keyForUpdate;
id observedObjectForForwarding;
NSString *keyForForwarding;
NSString *keyPathToForward;
}
最后塞椎,當(dāng)使用key1.key2.key3
作為keypath時(shí)桨仿,會(huì)利用NSKeyValueObservationForwarder
來(lái)轉(zhuǎn)發(fā),會(huì)生成如下關(guān)系:
被觀察者 | 觀察者 | keyPath |
---|---|---|
self | forwarder1 | key1 |
key1 | forwarder2 | key2 |
key2 | forwarder3 | key3 |
其中觀察者會(huì)依次相互引用
- (void) observeValueForKeyPath: (NSString *)keyPath
ofObject: (id)anObject
change: (NSDictionary *)change
context: (void *)context
{
if (anObject == observedObjectForUpdate)
{
[self keyPathChanged: nil];
}
else
{
[target observeValueForKeyPath: keyPathToForward
ofObject: observedObjectForUpdate
change: change
context: contextToForward];
}
}
在收到通知時(shí)會(huì)進(jìn)行轉(zhuǎn)發(fā)案狠,所以當(dāng)key3
進(jìn)行變化時(shí)服傍,forwarder3
---> forwarder2
---> forwarder1
---> 應(yīng)用層注冊(cè)的target