KVC/KVO是觀(guān)察者模式的一種實(shí)現(xiàn)蚁滋,在Cocoa中是以被萬(wàn)物之源NSObject類(lèi)實(shí)現(xiàn)的NSKeyValueCoding/NSKeyValueObserving非正式協(xié)議的形式被定義為基礎(chǔ)框架的一部分。從協(xié)議的角度來(lái)說(shuō)赵讯,KVC/KVO本質(zhì)上是定義了一套讓我們?nèi)プ袷睾蛯?shí)現(xiàn)的方法。
當(dāng)然您没,KVC/KVO實(shí)現(xiàn)的根本是Objective-C的動(dòng)態(tài)性和runtime温算,另外,KVC/KVO機(jī)制離不開(kāi)訪(fǎng)問(wèn)器方法的實(shí)現(xiàn)鹿驼,這在后文中也有解釋欲低。
KVO詳解
該類(lèi)必須支持KVC,使用屬性的 setter getter 方法畜晰,或 key-path砾莱;(需要實(shí)現(xiàn)與該屬性對(duì)應(yīng)的getter 和 setter 方法和其他一些可選的方法。NSObject類(lèi)已經(jīng)幫我們實(shí)現(xiàn)了這些凄鼻,只要你的類(lèi)繼承自NSObject腊瑟,并且使用正常方式創(chuàng)建屬性聚假,這些屬性都是支持KVO的;(實(shí)例變量 檢測(cè)不到變化))
KVO通知發(fā)出方式分為“手動(dòng)”和“自動(dòng)”兩種方式闰非;
- 自動(dòng):自動(dòng)通知由NSObject默認(rèn)實(shí)現(xiàn)了膘格,一般情況下不需要我們寫(xiě)額外代碼,屬性改變后通知自動(dòng)發(fā)出财松;
- 手動(dòng):可自行控制瘪贱,重寫(xiě)該方法(NSObject類(lèi)目方法)
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey;
可控制具體針對(duì)哪些屬性辆毡;
網(wǎng)上好多說(shuō)手動(dòng)實(shí)現(xiàn)菜秦,需要寫(xiě)下面兩個(gè)方法,但是實(shí)際代碼驗(yàn)證過(guò)程發(fā)現(xiàn)舶掖,不寫(xiě)也沒(méi)事球昨;
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
有時(shí)候會(huì)存在這樣一種情況,一個(gè)屬性的改變依賴(lài)于別的一個(gè)或多個(gè)屬性的改變眨攘,也就是說(shuō)當(dāng)別的屬性改了主慰,這個(gè)屬性也會(huì)發(fā)出通知
需要我們重寫(xiě)下面方法:
// 當(dāng) B 或 C 屬性變化時(shí),A屬性也會(huì)發(fā)出通知并監(jiān)聽(tīng)到(依賴(lài)鍵)
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@“A”]) {
NSArray *affectingKeys = @[@“B”,@“C”];
keyPaths = [keyPaths setByAddingObjectsFromArray: affectingKeys]鲫售;
}
return keyPaths;
}
- KVO是基于runtime 實(shí)現(xiàn)的共螺;
- 當(dāng)某個(gè)類(lèi)的屬性被第一次觀(guān)察時(shí),系統(tǒng)會(huì)在運(yùn)行期動(dòng)態(tài)的創(chuàng)建該類(lèi)的一個(gè)派生類(lèi)龟虎,在這個(gè)派生類(lèi)中重寫(xiě)基類(lèi)中任何被觀(guān)察屬性的setter方法璃谨。派生類(lèi)在被重寫(xiě)的setter方法內(nèi)實(shí)現(xiàn)真正的通知機(jī)制;
- 如果原類(lèi)為 A鲤妥,那么生成的派生類(lèi)名為NSKVONotifying_A
object_getClassName(“類(lèi)對(duì)象”) 獲取到 NSKVONotifying_A 類(lèi)名- 每個(gè)類(lèi)對(duì)象(類(lèi)的實(shí)例)都有一個(gè)isa指針 指向當(dāng)前類(lèi)佳吞,當(dāng)一個(gè)類(lèi)對(duì)象的第一次被觀(guān)察,系統(tǒng)會(huì)將isa指針指向動(dòng)態(tài)生成的派生類(lèi)棉安,從而給被監(jiān)控屬性賦值時(shí)執(zhí)行派生類(lèi)的setter方法底扳;(參考3)
當(dāng)手動(dòng)創(chuàng)建時(shí):系統(tǒng)會(huì)發(fā)出下面警告
KVO failed to allocate class pair for name NSKVONotifying_LabColor, automatic key-value observing will not work for this class
詳解KVC
NSKeyValueCoding:一種通過(guò)名稱(chēng)或鍵 間接訪(fǎng)問(wèn)對(duì)象屬性或者給對(duì)象賦值的機(jī)制,而不需要明確的存取方法贡耽。這樣就可以在運(yùn)行時(shí)動(dòng)態(tài)在訪(fǎng)問(wèn)和修改對(duì)象的屬性衷模。
NSObject 的類(lèi)目方法。所有繼承自 NSObject的都支持KVC 這個(gè)機(jī)制蒲赂;
KVC是怎么尋找Key的阱冶?
例如:personal類(lèi) name屬性
setter
1、程序優(yōu)先調(diào)用屬性 set 方法滥嘴,代碼通過(guò) setter 方法完成設(shè)置木蹬。
2、如果沒(méi)有找到 setName:方法若皱,KVC機(jī)制會(huì)檢查镊叁,該方法
+(BOOL)accessInstanceVariablesDirectly
方法有沒(méi)有返回YES尘颓,默認(rèn)會(huì)返回YES,則這個(gè)時(shí)候KVC機(jī)制會(huì)搜索該類(lèi)里有沒(méi)有命名為“_name”晦譬、“_isName”疤苹、“name”、“isName”敛腌,
的成員變量卧土,
- (void)setValue:(id)value forUndefinedKey:(NSString *)key;
3韩脑、當(dāng)重寫(xiě)+(BOOL)accessInstanceVariablesDirectly
函數(shù)并返回NO時(shí)氢妈,那么只要沒(méi)有該屬性時(shí),即使聲明了相關(guān)的成員變量段多,也會(huì)執(zhí)行該setValue: forUndefinedKey:
函數(shù)首量;
GET 搜索模式
1、 這是valueForKey:的默認(rèn)實(shí)現(xiàn)进苍,給定一個(gè)key當(dāng)做輸入?yún)?shù)加缘,開(kāi)始下面的步驟,在這個(gè)接收valueForKey:方法調(diào)用的類(lèi)內(nèi)部進(jìn)行操作觉啊。
通過(guò)getter方法搜索實(shí)例拣宏,例如get, , is, _的拼接方案。按照這個(gè)順序杠人,如果發(fā)現(xiàn)符合的方法勋乾,就調(diào)用對(duì)應(yīng)的方法并拿著結(jié)果跳轉(zhuǎn)到第五步。否則嗡善,就繼續(xù)到下一步辑莫。
2、 如果沒(méi)有找到簡(jiǎn)單的getter方法罩引,則搜索其匹配模式的方法countOf各吨、objectInAtIndex:、AtIndexes:蜒程。
如果找到其中的第一個(gè)和其他兩個(gè)中的一個(gè)绅你,則創(chuàng)建一個(gè)集合代理對(duì)象伺帘,該對(duì)象響應(yīng)所有NSArray的方法并返回該對(duì)象。否則忌锯,繼續(xù)到第三步伪嫁。
代理對(duì)象隨后將NSArray接收到的countOf、objectInAtIndex:偶垮、AtIndexes:的消息給符合KVC規(guī)則的調(diào)用方张咳。
當(dāng)代理對(duì)象和KVC調(diào)用方通過(guò)上面方法一起工作時(shí),就會(huì)允許其行為類(lèi)似于NSArray一樣似舵。
3脚猾、如果沒(méi)有找到NSArray簡(jiǎn)單存取方法,或者NSArray存取方法組砚哗。則查找有沒(méi)有countOf龙助、enumeratorOf、memberOf:命名的方法蛛芥。
如果找到三個(gè)方法提鸟,則創(chuàng)建一個(gè)集合代理對(duì)象(NSKeyValueSet),該對(duì)象響應(yīng)所有NSSet方法并返回仅淑。否則称勋,繼續(xù)執(zhí)行第四步。
此代理對(duì)象隨后轉(zhuǎn)換countOf涯竟、enumeratorOf赡鲜、memberOf:方法調(diào)用到創(chuàng)建它的對(duì)象上。實(shí)際上庐船,這個(gè)代理對(duì)象和NSSet一起工作银酬,使得其表象上看起來(lái)是NSSet。
4筐钟、如果沒(méi)有發(fā)現(xiàn)簡(jiǎn)單getter方法捡硅,或集合存取方法組,以及接收類(lèi)方法accessInstanceVariablesDirectly是返回YES的盗棵。搜索一個(gè)名為_(kāi)壮韭、_is、纹因、is的實(shí)例喷屋,根據(jù)他們的順序。
如果發(fā)現(xiàn)對(duì)應(yīng)的實(shí)例瞭恰,則立刻獲得實(shí)例可用的值并跳轉(zhuǎn)到第五步屯曹,否則,跳轉(zhuǎn)到第六步。
5恶耽、如果取回的是一個(gè)對(duì)象指針密任,則直接返回這個(gè)結(jié)果。
如果取回的是一個(gè)基礎(chǔ)數(shù)據(jù)類(lèi)型偷俭,但是這個(gè)基礎(chǔ)數(shù)據(jù)類(lèi)型是被NSNumber支持的浪讳,則存儲(chǔ)為NSNumber并返回。
如果取回的是一個(gè)不支持NSNumber的基礎(chǔ)數(shù)據(jù)類(lèi)型涌萤,則通過(guò)NSValue進(jìn)行存儲(chǔ)并返回淹遵。
6、如果所有情況都失敗负溪,則調(diào)用valueForUndefinedKey:方法并拋出異常透揣,這是默認(rèn)行為。但是子類(lèi)可以重寫(xiě)此方法川抡。
關(guān)于nil值的處理
(1)如果屬性基本類(lèi)型是(int辐真、float、double)且傳入對(duì)應(yīng)的參數(shù)崖堤,如果value設(shè)置一個(gè)nil拆祈,就會(huì)發(fā)生異常。
(2)可通過(guò)重寫(xiě)-(void)setNilValueForKey:(NSString *)key
系統(tǒng)函數(shù)倘感,來(lái)做相關(guān)異常處理;
KVC 集合運(yùn)算符
- Simple Collection Operators 簡(jiǎn)單的集合操作符
- Object Operators 對(duì)象操作符
- Array and Set Operators 數(shù)組/集合操作符
1咙咽、@count 返回一個(gè)值為集合中對(duì)象總數(shù)的NSNumber對(duì)象;
2老玛、@avg 首先把集合中的每個(gè)對(duì)象都轉(zhuǎn)換為double類(lèi)型,然后計(jì)算其平均值,并返回這個(gè)平均值的NSNumber對(duì)象;
3、@max 使用compare:方法來(lái)確定最大值,并返回最大值的NSNumber對(duì)象.所以為了保證其正常比較,集合中所有的對(duì)象都必須支持和另一個(gè)對(duì)象的比較,保證其可比性;
4钧敞、@min 原理和@max一樣,其返回的是集合中的最小值的NSNumber對(duì)象;
5蜡豹、@sum 首先把集合中的每個(gè)對(duì)象都轉(zhuǎn)換為double類(lèi)型,然后計(jì)算其總和,并返回總和的NSNumber對(duì)象;
Simple Collection Operators 簡(jiǎn)單的集合操作符
// kvc 集合操作(基本數(shù)據(jù)類(lèi)型)
KVCCollectionOperatorsTest *collectionTest = [[KVCCollectionOperatorsTest alloc] init];
collectionTest.name = @"測(cè)試1";
collectionTest.count = 10;
KVCCollectionOperatorsTest *collectionTest2 = [[KVCCollectionOperatorsTest alloc] init];
collectionTest2.name = @"測(cè)試2";
collectionTest2.count = 20;
KVCCollectionOperatorsTest *collectionTest3 = [[KVCCollectionOperatorsTest alloc] init];
collectionTest3.name = @"測(cè)試3";
collectionTest3.count = 30;
NSArray *collectionArr = @[collectionTest, collectionTest2, collectionTest3];
NSNumber *countNum = [collectionArr valueForKeyPath:@"@count"];
NSNumber *avgNum = [collectionArr valueForKeyPath:@"@avg.count"];
NSNumber *maxNum = [collectionArr valueForKeyPath:@"@max.count"];
NSNumber *minNum = [collectionArr valueForKeyPath:@"@min.count"];
NSNumber *sunNum = [collectionArr valueForKeyPath:@"@sum.count"];
// 若操作對(duì)象(數(shù)組/集合)內(nèi)的元素本身就是 NSNumber 對(duì)象,那么可以這樣寫(xiě).
NSArray *collectionArr2 = @[@(collectionTest.count), @(collectionTest2.count), @(collectionTest3.count)];
NSNumber *countNum2 = [collectionArr2 valueForKeyPath:@"@count"];
NSNumber *avgNum2 = [collectionArr2 valueForKeyPath:@"@avg.self"];
NSNumber *maxNum2 = [collectionArr2 valueForKeyPath:@"@max.self"];
NSNumber *minNum2 = [collectionArr2 valueForKeyPath:@"@min.self"];
NSNumber *sunNum2 = [collectionArr2 valueForKeyPath:@"@sum.self"];
Object Operators 對(duì)象操作符
-
@unionOfObjects:
獲取數(shù)組中每個(gè)對(duì)象的屬性的值,放到一個(gè)數(shù)組中并返回,但不會(huì)去重; -
@distinctUnionOfObjects:
獲取數(shù)組中每個(gè)對(duì)象的屬性的值,放到一個(gè)數(shù)組中并返回,會(huì)對(duì)數(shù)組去重.所以,通常這個(gè)對(duì)象操作符可以用來(lái)對(duì)數(shù)組元素的去重,快捷高效;
NSArray *unionOfObjects = [collectionArr valueForKeyPath:@"@unionOfObjects.name"];
NSArray *distinctUnionOfObjects = [collectionArr valueForKeyPath:@"@distinctUnionOfObjects.name"];
NSLog(@"unionOfObjects = %@",unionOfObjects);
NSLog(@"distinctUnionOfObjects = %@",distinctUnionOfObjects);
// 輸出
2018-04-16 16:59:11.891950+0800 KVODemo[8751:365605] unionOfObjects = (
aaa,
bbb,
aaa
)
2018-04-16 16:59:12.791130+0800 KVODemo[8751:365605] distinctUnionOfObjects = (
aaa,
bbb
)
Array and Set Operators 數(shù)組和集合操作符
數(shù)組和集合操作符作用對(duì)象是嵌套的集合,也就是說(shuō)溉苛,是一個(gè)集合且其內(nèi)部每個(gè)元素是一個(gè)集合镜廉。數(shù)組和集合操作符包括 @distinctUnionOfArrays
,@unionOfArrays
愚战,@distinctUnionOfSets:
-
@distinctUnionOfArrays
娇唯、@unionOfArrays
返回一個(gè)數(shù)組,其中包含這個(gè)集合中每個(gè)數(shù)組對(duì)于這個(gè)操作符右面指定的 keyPath 進(jìn)行操作之后的值寂玲。distinct 會(huì)移除重復(fù)的值塔插。 -
@distinctUnionOfSets
和@distinctUnionOfArrays
差不多,但是它期望的是一個(gè)包含著NSSet對(duì)象的NSSet拓哟,并且會(huì)返回一個(gè)NSSet對(duì)象想许。因?yàn)榧喜荒馨貜?fù)的值,所以只有distinct操作;
/*
數(shù)組和集合操作符
* @distinctUnionOfArrays:
* @unionOfArrays:
* @distinctUnionOfSets:
*/
KVCCollectionOperatorsTest *arrAndSetTest = [[KVCCollectionOperatorsTest alloc] init];
arrAndSetTest.name = @"aaa";
arrAndSetTest.count = 10;
KVCCollectionOperatorsTest *arrAndSetTest2 = [[KVCCollectionOperatorsTest alloc] init];
arrAndSetTest2.name = @"bbb";
arrAndSetTest2.count = 20;
KVCCollectionOperatorsTest *arrAndSetTest3 = [[KVCCollectionOperatorsTest alloc] init];
arrAndSetTest3.name = @"aaa";
arrAndSetTest3.count = 30;
KVCCollectionOperatorsTest *arrAndSetTest4 = [[KVCCollectionOperatorsTest alloc] init];
arrAndSetTest4.name = @"arrAndSetTest";
arrAndSetTest4.count = 10;
KVCCollectionOperatorsTest *arrAndSetTest5 = [[KVCCollectionOperatorsTest alloc] init];
arrAndSetTest5.name = @"arrAndSetTest2";
arrAndSetTest5.count = 20;
KVCCollectionOperatorsTest *arrAndSetTest6 = [[KVCCollectionOperatorsTest alloc] init];
arrAndSetTest6.name = @"arrAndSetTest3";
arrAndSetTest6.count = 30;
NSArray *arrayAndSetCollectionArr = @[arrAndSetTest, arrAndSetTest2, arrAndSetTest3];
NSArray *arrayAndSetCollectionArr2 = @[arrAndSetTest4, arrAndSetTest5, arrAndSetTest6];
NSMutableArray *totalCount = [NSMutableArray array];
[totalCount addObject:arrayAndSetCollectionArr];
[totalCount addObject:arrayAndSetCollectionArr2];
NSLog(@"--- %@",[totalCount valueForKeyPath:@"@unionOfArrays.name"]);
NSLog(@"+++ %@",[totalCount valueForKeyPath:@"@distinctUnionOfArrays.name"]);
輸出
2018-04-17 10:23:41.399298+0800 KVODemo[1280:55204] --- (
aaa,
bbb,
aaa,
arrAndSetTest,
arrAndSetTest2,
arrAndSetTest3
)
2018-04-17 10:23:41.936263+0800 KVODemo[1280:55204] +++ (
bbb,
aaa,
arrAndSetTest,
arrAndSetTest2,
arrAndSetTest3
)