iOS觀(guān)察模式之KVO實(shí)現(xiàn)探究

本篇會(huì)對(duì)KVO的實(shí)現(xiàn)進(jìn)行探究,不涉及太多KVO的使用方法,但是會(huì)有一些使用時(shí)的思考。

一、使用上的疑問(wèn)

1.keyPath是什么

當(dāng)我們使用@property時(shí)候,keyPath是指的是我們的屬性名,實(shí)例變量或者是存取方法?

:point_down: 對(duì)一個(gè)屬性值使用@synthesize重新定義了存儲(chǔ)變量

```

# import"Person.h"

@interface Student:Person

@property(nonatomic,strong)NSString* mark;

@end

@implementation ?Student

@synthesizemark = abc;

- (void)setMark:(NSString*)newMark {

?abc = newMark;

}

- (NSString*)mark {

returnabc;

}

main() {?

Student *stu = [[Student alloc] init];?

stu.mark =@"65";

?StudentKvoObserver *stuObserver = [[StudentKvoObserver alloc] initWithStudent:stu]; [stuObserver addObserverForKeyPath:@"mark"];

```

// 重命名get方法stu.mark =@"85";}

實(shí)際結(jié)果是,能夠監(jiān)聽(tīng)到mark值的變化,反之,我將mark替換成真正的實(shí)例變量abc時(shí),無(wú)法獲取狀態(tài)。

現(xiàn)在想想其實(shí)答案早就存在了,我們不做顯示的@synthesize的指定時(shí),其實(shí)等價(jià)于 @synthesize mark = _mark; ,由此看來(lái)keyPath實(shí)際指的并不是真正存儲(chǔ)你數(shù)據(jù)的變量。

2.KVO是否能夠繼承

我是否能夠監(jiān)聽(tīng)我父類(lèi)里的屬性,哪怕他并沒(méi)有暴露出來(lái)?通過(guò)某些手段得(猜)到了keyPath,然后去監(jiān)聽(tīng)它甚至是KVC修改他的值。

子類(lèi)繼承父類(lèi)的一個(gè)屬性,當(dāng)這個(gè)屬性被改變時(shí),KVO能否觀(guān)察到?

子類(lèi)繼承父類(lèi)的一個(gè)未暴露的屬性,當(dāng)這個(gè)屬性被改變時(shí),KVO能否觀(guān)察到?

子類(lèi)繼承父類(lèi)屬性并重寫(xiě)了它的setter方法,當(dāng)這個(gè)屬性被改變時(shí),KVO能否觀(guān)察到?

// Person類(lèi)@interfacePerson:NSObject

@property(nonatomic,strong)NSString*firstName;

@property(nonatomic,strong)NSString*lastName;

@property(nonatomic,strong,readonly)NSString*fullName;

- (void)setNewInnerName:(NSString*)str;

@end

@interfacePerson()

@property(nonatomic,strong)NSString*innerName;

@end@implementationPerson

- (void)setNewInnerName:(NSString*)str {

self.innerName = str;// 通過(guò)get、set訪(fǎng)問(wèn) 觸發(fā)KVO

// [self setValue:str forKey:@"innerName"];

// KVC方式,其實(shí)調(diào)用的也是setter方法 觸發(fā)KVO

// _innerName = str;

// 直接訪(fǎng)問(wèn)成員變量,不觸發(fā)KVO}

// Student類(lèi)

@interface ?Student:Person

@end

@implementationStudent

- (void)setFirstName:(NSString*)firstName {

NSLog(@"重寫(xiě)的setFirstName方法");

}

@end

// 執(zhí)行文件

main() {

?Person *p = [[Person alloc] init];?

p.firstName =@"zhao";?

p.lastName =@"zhiyu";

?PersonKvoObserver *personKvoObserver = [[PersonKvoObserver alloc]initWithPerson:p];?

[personKvoObserver addObserverForKeyPath:@"fullName"];

// 屬性關(guān)聯(lián)

[personKvoObserver addObserverForKeyPath:@"innerName"];

// 內(nèi)部屬性

p.firstName =@"zhao1";

?[p setNewInnerName:@"newInnerNmame"];

// 沒(méi)有暴露的屬性的get、set方法被調(diào)用時(shí),也會(huì)發(fā)送通知// 子類(lèi)的屬性監(jiān)聽(tīng)

Student *stu = [[Student alloc] init];

?stu.firstName =@"stu";?

stu.lastName =@"dent";

?StudentKvoObserver *stuObserver = [[StudentKvoObserver alloc] initWithStudent:stu]; [stuObserver addObserverForKeyPath:@"fullName"];// 子類(lèi)繼承屬性依舊被監(jiān)聽(tīng)[stuObserver addObserverForKeyPath:@"firstName"];// 重寫(xiě)方法,不加super,依舊會(huì)監(jiān)聽(tīng)kvo[stuObserver addObserverForKeyPath:@"innerName"];??

stu.firstName =@"stu1";

stu.lastName =@"dent1";

?[stu setNewInnerName:@"newInnerNmame"];

// 沒(méi)有暴露的屬性的get、set方法被調(diào)用時(shí),也會(huì)發(fā)送通知}

通過(guò)上面的例子,我們能看出幾點(diǎn):

①通過(guò)KVO,能觀(guān)察父類(lèi)的屬性值。

②只要知道了keyPath,不管有沒(méi)有暴露方法,依舊可以通過(guò)KVO方式觀(guān)察值的變化,而且同屬性一樣,可以被繼承。

③子類(lèi)重寫(xiě)父類(lèi)的set方法,也并不會(huì)影響KVO的觀(guān)察。

從這兒開(kāi)始就有點(diǎn)好奇了,這個(gè)KVO是否通過(guò)子類(lèi)化的方法實(shí)現(xiàn)?那如何讓子類(lèi)的繼承屬性也能被監(jiān)聽(tīng)到?了解到KVO依賴(lài)setter方法的重寫(xiě),那我子類(lèi)重寫(xiě)的setter方法之后,為什么子類(lèi)繼承屬性的監(jiān)聽(tīng)依然生效?

3.跨線(xiàn)程的監(jiān)聽(tīng)

我們知道使用Notification時(shí),跨線(xiàn)程發(fā)送通知是無(wú)法被接受到的,那么現(xiàn)在看看KVO在多線(xiàn)程中的表現(xiàn)。

// 在兩個(gè)線(xiàn)程定義目標(biāo)和觀(guān)察者

dispatch_queue_t ?concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);

// dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

__block Student *stu1 =nil;

dispatch_async(concurrentQueue, ^{// 對(duì)象屬性

stu1 = [[Student alloc] init];

NSLog(@"Student %@",[NSDate ?new]);?

stu1.lastName =@"yyyyyyy";?

});

__block StudentKvoObserver *stuObserver1;

dispatch_async(concurrentQueue, ^{?

sleep(2);?

stuObserver1 = [[StudentKvoObserver alloc] initWithStudent:stu1];

?[stuObserver1 addObserverForKeyPath:@"fullName"];

// 子類(lèi)繼承屬性依舊被監(jiān)聽(tīng)

NSLog(@" StudentKvoObserver %@",[NSDate new]); }); dispatch_barrier_async(concurrentQueue, ^{

NSLog(@"dispatch_barrier_async %@",[NSDate new]);

NSLog(@"zzzzzz start%@",[NSDate new]);

?stu1.lastName =@"zzzzzz";

NSLog(@"zzzzzz end%@",[NSDate new]); });

輸出結(jié)果

2016-10-11 10:46:53.319 KVCLearn[3364:331572] Student 2016-10-11 02:46:53 +0000

2016-10-11 10:46:55.324 KVCLearn[3364:331578] StudentKvoObserver 2016-10-11 02:46:55 +0000

2016-10-11 10:46:55.325 KVCLearn[3364:331578] dispatch_barrier_async 2016-10-11 02:46:55 +0000

2016-10-11 10:46:55.325 KVCLearn[3364:331578] zzzzzz start2016-10-11 02:46:55 +0000

2016-10-11 10:46:55.326 KVCLearn[3364:331578] fullName

{

kind = 1;

new = “(null)zzzzzz”;

old = “(null)yyyyyyy”;

}

2016-10-11 10:46:55.326 KVCLearn[3364:331578] zzzzzz end2016-10-11 02:46:55 +0000

可以看到在兩個(gè)不同的線(xiàn)程里創(chuàng)建的Observer和Target,觀(guān)察變化也是能夠生效的。

這里有一個(gè)關(guān)于GCD的問(wèn)題,這里我使用了dispatch_barrier_async,分發(fā)到自定義的并發(fā)隊(duì)列上,這時(shí)barrier是正常工作的,保證了第三個(gè)task在前兩個(gè)執(zhí)行完之后執(zhí)行。但是當(dāng)我直接使用系統(tǒng)全局的并發(fā)隊(duì)列時(shí),barrier不起作用,不能保證他們的執(zhí)行順序。這里希望有高人看見(jiàn)了能解答下。

二、實(shí)現(xiàn)探究

1.API接口

Foundation里關(guān)于KVO的部分都定義在NSKeyValueObserving.h中,KVO通過(guò)以下三個(gè)NSObject分類(lèi)實(shí)現(xiàn)。

NSObject(NSKeyValueObserving)

NSObject(NSKeyValueObserverRegistration)

NSObject(NSKeyValueObservingCustomization)

這里會(huì)從NSObject (NSKeyValueObserverRegistration) 的 - addObserver:forKeyPath:options:context: 為入口,去一步步分析如何整個(gè)KVO的實(shí)現(xiàn)方式。

2.先說(shuō)結(jié)論

實(shí)現(xiàn)方式:

一個(gè)對(duì)象在被調(diào)用addObserver方法時(shí),會(huì)動(dòng)態(tài)創(chuàng)建一個(gè)KVO前綴的原類(lèi)的子類(lèi),用來(lái)重寫(xiě)所有的setter方法,并且該子類(lèi)的 - (Class) class 和 - (Class) superclass 方法會(huì)被重寫(xiě),返回父類(lèi)(原始類(lèi))的Class。最后會(huì)將當(dāng)前對(duì)象的類(lèi)改為這個(gè)KVO前綴的子類(lèi)。

比較繞,讓我們來(lái)看個(gè)例子。比如說(shuō)類(lèi)Person的實(shí)例person調(diào)用了addObserver方法時(shí),addObserver方法內(nèi)部給你創(chuàng)建了一個(gè)KVOPerson類(lèi),KVOPerson的所有的setter方法會(huì)被重寫(xiě),它的class和superClass方法會(huì)被改寫(xiě)成返回Person和NSObject,之后使用 object_setClass 將KVOPerson設(shè)置成person的class。

當(dāng)我們調(diào)用person的setName方法時(shí),實(shí)際是調(diào)用的一個(gè)KVOPerson實(shí)例的setName方法,但由于重寫(xiě)了class,在外部看不出來(lái)其中的差別。在setter方法中,我們?cè)趯?shí)際值被改變的前后回調(diào)用 - (void)willChangeValueForKey:(NSString *)key; 和 - (void)didChangeValueForKey:(NSString *)key; 方法,通知觀(guān)察者值的變化。

3.代碼

源碼是來(lái)自GNUSetup里的Foundation,據(jù)說(shuō)和apple的實(shí)現(xiàn)類(lèi)似,只是相關(guān)API的版本會(huì)比較老一些。我們先從addObserver方法開(kāi)始。

@implementationNSObject(NSKeyValueObserverRegistration)- (void) addObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath options: (NSKeyValueObservingOptions)options context: (void*)aContext{ ....// 1.使用當(dāng)前類(lèi)創(chuàng)建GSKVOReplacement對(duì)象r = replacementForClass([selfclass]); .... info = (GSKVOInfo*)[selfobservationInfo];if(info ==nil) { info = [[GSKVOInfo alloc] initWithInstance:self]; [selfsetObservationInfo: info];//2.重新設(shè)置classobject_setClass(self, [r replacement]); } ....//3.重寫(xiě)replace的setter方法[r overrideSetterFor: aPath];//4.注冊(cè)當(dāng)前類(lèi)和觀(guān)察者到全局表中[info addObserver: anObserver forKeyPath: aPath options: options context: aContext];}

忽略了一些分支,可以看到主要為上面四個(gè)步驟。我們可以一個(gè)一個(gè)拆開(kāi)來(lái)看。

replacementForClass

// 單例生成一個(gè)GSKVOReplacement對(duì)象,保證一個(gè)類(lèi)只有一個(gè)KVO子類(lèi)staticGSKVOReplacement *replacementForClass(Class c){ GSKVOReplacement *r; setup(); [kvoLock lock]; r = (GSKVOReplacement*)NSMapGet(classTable, (void*)c);if(r ==nil) { r = [[GSKVOReplacement alloc] initWithClass: c];NSMapInsert(classTable, (void*)c, (void*)r); } [kvoLock unlock];returnr;}- (id) initWithClass: (Class)aClass{NSValue*template;NSString*superName;NSString*name; original = aClass; superName =NSStringFromClass(original); name = [@"GSKVO"stringByAppendingString: superName];// 添加前綴template = GSObjCMakeClass(name, superName,nil);// 通過(guò)objc_allocateClassPair得到class指針GSObjCAddClasses([NSArrayarrayWithObject: template]);// objc_registerClassPair注冊(cè)classreplacement =NSClassFromString(name);// 前面動(dòng)態(tài)生成且注冊(cè)了GSKVO子類(lèi),然后就可以通過(guò)該方法得到// 添加模板類(lèi)的一些方法,包括重寫(xiě)class和superClass讓對(duì)象類(lèi)型不暴露,// setValue:forkey在數(shù)據(jù)改變前后加上willChange和didChange方法GSObjCAddClassBehavior(replacement, baseClass);/* Create the set of setter methods overridden.

*/keys = [NSMutableSetnew];returnself;}

object_setClass(self, [r replacement]);

// replace就是新生成的KVOXXX的class@interfaceGSKVOReplacement:NSObject{ Class original;/* The original class */Class replacement;/* The replacement class */NSMutableSet*keys;/* The observed setter keys */}replacement =NSClassFromString(name);// 在initWithClass方法中賦值

overrideSetterFor

重寫(xiě)setter方法,在值改變前后添加上willChange&didChange- (void) overrideSetterFor: (NSString*)aKey{if([keys member: aKey] ==nil) {NSMethodSignature*sig;// 當(dāng)前key值對(duì)應(yīng)setter的方法簽名SEL sel;// 當(dāng)前key值對(duì)應(yīng)setter的方法名selectorIMP imp;// 當(dāng)前key值對(duì)應(yīng)setter的函數(shù)指針I(yè)MPconstchar*type;NSString*a[2];unsignedi;BOOLfound =NO;// 得到setXxxx:和_setXxxx:方法名a[0] = [NSStringstringWithFormat:@"set%@%@:", tmp, suffix]; a[1] = [NSStringstringWithFormat:@"_set%@%@:", tmp, suffix];for(i =0; i <2; i++) {/*

得到方法簽名

*/sel =NSSelectorFromString(a[i]); sig = [original instanceMethodSignatureForSelector: sel]; type = [sig getArgumentTypeAtIndex:2];// 第三個(gè)參數(shù)即入?yún)⒌念?lèi)型switch(*type) {// 字符case_C_CHR:case_C_UCHR: imp = [[GSKVOSetter class] instanceMethodForSelector:@selector(setterChar:)];// 返回setterChar:函數(shù)的函數(shù)指針I(yè)MPbreak;// 對(duì)象、類(lèi)、指針case_C_ID:case_C_CLASS:case_C_PTR: imp = [[GSKVOSetter class] instanceMethodForSelector:@selector(setter:)];// 返回setter:函數(shù)的函數(shù)指針I(yè)MP,后面有詳解break;break; ....default: imp =0;break; }if(imp !=0) {if(class_addMethod(replacement, sel, imp, [sig methodType]))// 將原sel和新imp加到replacement類(lèi)中去{ found =YES; }else{NSLog(@"Failed to add setter method for %s to %s", sel_getName(sel), class_getName(original)); } } }if(found ==YES) { [keys addObject: aKey]; } }}

這個(gè)步驟是將keypath對(duì)應(yīng)的setter方法重寫(xiě)找出來(lái),把原有的SEL函數(shù)名和重寫(xiě)后的實(shí)現(xiàn)IMP加入到子類(lèi)中去。這樣做,新生成的子類(lèi)就有和原父類(lèi)一樣表現(xiàn)了,再加上之前的class替換,在KVO的對(duì)外接口上已經(jīng)沒(méi)有差別。這里也解釋了我一開(kāi)始的問(wèn)題,keypath到底指的是什么,其實(shí)是setter方法,或者說(shuō)方法名的后綴。因?yàn)槲覀冇聾property生成了默認(rèn)的set方法是滿(mǎn)足規(guī)范的,所以會(huì)將keypath和property關(guān)聯(lián)起來(lái)。

// setter方法的實(shí)現(xiàn)細(xì)節(jié)@implementationGSKVOSetter- (void) setter: (void*)val{NSString*key; Class c = [selfclass];void(*imp)(id,SEL,void*); imp = (void(*)(id,SEL,void*))[c instanceMethodForSelector: _cmd]; key = newKey(_cmd);if([c automaticallyNotifiesObserversForKey: key] ==YES) {// pre setting code here[selfwillChangeValueForKey: key]; (*imp)(self, _cmd, val);// post setting code here[selfdidChangeValueForKey: key]; }else{ (*imp)(self, _cmd, val); } RELEASE(key);}

對(duì)于這個(gè)setter方法的實(shí)現(xiàn),我其實(shí)是沒(méi)大看懂的。 [c instanceMethodForSelector: _cmd]; 這個(gè)取到的imp,應(yīng)該是當(dāng)前方法的函數(shù)指針(GSKVOSetter的setter),后面也是直接調(diào)用的該imp實(shí)現(xiàn)。沒(méi)有找到這個(gè)setter是如何和原類(lèi)方法中實(shí)際的setter聯(lián)系起來(lái)的,之前通過(guò)sig方法簽名也只取出了sel,原有實(shí)現(xiàn)并沒(méi)有出現(xiàn)。希望有大??吹竭@個(gè)能給我解答一下。

-(void) addObserver: forKeyPath: options: context:

這個(gè)部分就是觀(guān)察者的注冊(cè)了。通過(guò)以下類(lèi)圖可以很方便得看到,所有的類(lèi)的KVO觀(guān)察都是通過(guò)infoTable管理的。以被觀(guān)察對(duì)象實(shí)例作key,GSKVOInfo對(duì)象為value的形式保存在infoTable表里,每個(gè)被觀(guān)察者實(shí)例會(huì)對(duì)應(yīng)多個(gè)keypath,每個(gè)keypath會(huì)對(duì)應(yīng)多個(gè)observer對(duì)象。順帶提一下,關(guān)于Notification的實(shí)現(xiàn)也類(lèi)似,也是全局表維護(hù)通知的注冊(cè)監(jiān)聽(tīng)者和通知名。

GSKVOInfo的結(jié)構(gòu)可以看出來(lái),一個(gè)keyPath可以對(duì)應(yīng)有多個(gè)觀(guān)察者。其中觀(guān)察對(duì)象的實(shí)例和option打包成GSKVOObservation對(duì)象保存在一起。

三、總結(jié)

看完了KVO的實(shí)現(xiàn)部分,我們?cè)倩剡^(guò)頭來(lái)看開(kāi)頭提到的幾個(gè)問(wèn)題。

keyPath是什么

首先keyPath,是對(duì)于setter方法的關(guān)聯(lián),會(huì)使用keypath作為后綴去尋找原類(lèi)的setter方法的方法簽名,和實(shí)際存取對(duì)象和property名稱(chēng)沒(méi)有關(guān)系。所以這也是為什么我們重命名了setter方法之后,沒(méi)有辦法再去使用KVO或KVC了,需要手動(dòng)調(diào)用一次willChangeValue方法。

子類(lèi)繼承父類(lèi)的一個(gè)屬性,當(dāng)這個(gè)屬性被改變時(shí),KVO能否觀(guān)察到?

因?yàn)槔^承的關(guān)系Father <- Son <- KVOSon,當(dāng)我監(jiān)聽(tīng)一個(gè)父類(lèi)屬性的keyPath的時(shí)候,Son實(shí)例同樣可以通過(guò)消息查找找到父類(lèi)的setter方法,再將該方法加入到KVOSon類(lèi)當(dāng)中去。

子類(lèi)繼承父類(lèi)的一個(gè)未暴露的屬性,當(dāng)這個(gè)屬性被改變時(shí),KVO能否觀(guān)察到?

由于在overrideSetterFor中,我們是直接通過(guò)sel去得到方法簽名signature,所以和暴不暴露沒(méi)啥關(guān)系。

子類(lèi)繼承父類(lèi)屬性并重寫(xiě)了它的setter方法,當(dāng)這個(gè)屬性被改變時(shí),KVO能否觀(guān)察到?

在上一條中知道,其實(shí)子類(lèi)監(jiān)聽(tīng)父類(lèi)屬性,并不依賴(lài)?yán)^承,而是通過(guò)ISA指針在消息轉(zhuǎn)發(fā)的時(shí)候能夠獲取到父類(lèi)方法就足夠。所以當(dāng)我們重寫(xiě)父類(lèi)setter方法,相當(dāng)于在子類(lèi)定義了該setter函數(shù),在我們?nèi)ビ胹el找方法簽名時(shí),直接在子類(lèi)中就拿到了,甚至都不需要去到父類(lèi)里。所以理解了KVO監(jiān)聽(tīng)父類(lèi)屬性和繼承沒(méi)有直接聯(lián)系這一點(diǎn),就不再糾結(jié)set方法是否重寫(xiě)這個(gè)問(wèn)題了。

最后線(xiàn)程安全的部分,沒(méi)有做深入的研究,在這篇就不多做表述了。在我貼的源碼中都去掉了很多枝葉,其中就包括加鎖的部分。有興趣的朋友可以去下面貼的源碼地址去看完整版,其中對(duì)線(xiàn)程安全的考慮,遞歸鎖、惰性遞歸鎖使用,也是很值得學(xué)習(xí)的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,030評(píng)論 0 9
  • 本篇會(huì)對(duì)KVO的實(shí)現(xiàn)進(jìn)行探究,不涉及太多KVO的使用方法,但是會(huì)有一些使用時(shí)的思考。 一、使用上的疑問(wèn) 1.key...
    孢子菌閱讀 2,255評(píng)論 7 13
  • 1、category和extension的區(qū)別 category是分類(lèi),可以為類(lèi)增加自定義方法 extension...
    大猿媛閱讀 401評(píng)論 1 1
  • “來(lái),咱爺倆干一杯?!?記憶里,除父親之外,不曾有人對(duì)我以“咱爺倆”相稱(chēng),更何況,父親離開(kāi)我已十年了。這是十年來(lái)我...
    海的波文閱讀 514評(píng)論 10 24
  • 2017.07.29 星期六 多云 突然的炎熱讓我有一絲絲的不適應(yīng)。 這雨?大概下了一個(gè)月了吧,纏纏綿綿,絲絲縷...
    若璃殤閱讀 332評(píng)論 3 2

友情鏈接更多精彩內(nèi)容