集合屬性操作
根據(jù)KVO的實(shí)現(xiàn)原理当窗,是在運(yùn)行時(shí)生成新的子類并重寫其setter方法,在其內(nèi)容發(fā)生改變時(shí)發(fā)送消息。但這只是對(duì)屬性直接進(jìn)行賦值會(huì)觸發(fā)峡谊,如果屬性是容器對(duì)象,對(duì)容器對(duì)象進(jìn)行add或remove操作刊苍,則不會(huì)調(diào)用KVO的方法既们。可以通過(guò)KVC對(duì)應(yīng)的API來(lái)配合使用正什,使容器對(duì)象內(nèi)部發(fā)生改變時(shí)也能觸發(fā)KVO啥纸。
在進(jìn)行容器對(duì)象操作時(shí),先調(diào)用下面方法通過(guò)key或者keyPath獲取集合對(duì)象婴氮,然后再對(duì)容器對(duì)象進(jìn)行add或remove等操作時(shí)斯棒,就會(huì)觸發(fā)KVO的消息通知了。
-(NSMutableArray*)mutableArrayValueForKey:(NSString*)key;-(NSMutableOrderedSet*)mutableOrderedSetValueForKey:(NSString*)keyAPI_AVAILABLE(macos(10.7),ios(5.0),watchos(2.0),tvos(9.0));-(NSMutableSet*)mutableSetValueForKey:(NSString*)key;
keyPath方法:
-(NSMutableArray*)mutableArrayValueForKeyPath:(NSString*)keyPath;-(NSMutableOrderedSet*)mutableOrderedSetValueForKeyPath:(NSString*)keyPathAPI_AVAILABLE(macos(10.7),ios(5.0),watchos(2.0),tvos(9.0));-(NSMutableSet*)mutableSetValueForKeyPath:(NSString*)keyPath;
集合運(yùn)算符
KVC提供的valueForKeyPath:方法非常強(qiáng)大主经,可以通過(guò)該方法對(duì)集合對(duì)象進(jìn)行“深入”操作荣暮,在其keyPath中嵌套集合運(yùn)算符,例如求一個(gè)數(shù)組中對(duì)象某個(gè)屬性的count罩驻。(集合對(duì)象主要指NSArray和NSSet穗酥,但不包括NSDictionary)
集合運(yùn)算符格式
上面表達(dá)式主要分為三部分,left部分是要操作的集合對(duì)象,如果調(diào)用KVC的對(duì)象本來(lái)就是集合對(duì)象迷扇,則left可以為空百揭。中間部分是表達(dá)式,表達(dá)式一般以@符號(hào)開頭蜓席。后面是進(jìn)行運(yùn)算的屬性器一。
集合運(yùn)算符主要分為三類:
集合操作符:處理集合包含的對(duì)象,并根據(jù)操作符的不同返回不同的類型厨内,返回值以NSNumber為主祈秕。
數(shù)組操作符:根據(jù)操作符的條件,將符合條件的對(duì)象包含在數(shù)組中返回雏胃。
嵌套操作符:處理集合對(duì)象中嵌套其他集合對(duì)象的情況请毛,返回結(jié)果也是一個(gè)集合對(duì)象。
example
下面是為了方便模擬KVC操作瞭亮,而創(chuàng)建的測(cè)試代碼方仿。定義Transaction類為模型類,類中包含三種類型的屬性统翩。并定義BankAccount類仙蚜,其中包含一個(gè)數(shù)組,下面的代碼示例就都是操作這個(gè)數(shù)組的厂汗,并且數(shù)組包含所有Transaction對(duì)象委粉。
@interfaceTransaction:NSObject@property(nonatomic,strong)NSString*payee;@property(nonatomic,strong)NSNumber*amount;@property(nonatomic,strong)NSDate*date;@end
@interfaceBankAccount:NSObject@property(nonatomic,strong)NSArray*transactions;@end
集合操作符
集合操作符處理NSArray和NSSet及其子類這樣的集合對(duì)象,并根據(jù)不同的操作符返回不同類型的對(duì)象娶桦,返回值一般都是NSNumber贾节。
@avg用來(lái)計(jì)算集合中right keyPath指定的屬性的平均值。
NSNumber*transactionAverage = [self.transactions valueForKeyPath:@"@avg.amount"];
@count用來(lái)計(jì)算集合的總數(shù)衷畦。
NSNumber*numberOfTransactions = [self.transactions valueForKeyPath:@"@count"];
備注:@count操作符比較特殊栗涂,它不需要寫right keyPath,即使寫了也會(huì)被忽略祈争。
@sum用來(lái)計(jì)算集合中right keyPath指定的屬性的總和斤程。
NSNumber*amountSum = [self.transactions valueForKeyPath:@"@sum.amount"];
@max用來(lái)查找集合中right keyPath指定的屬性的最大值。
NSDate*latestDate = [self.transactions valueForKeyPath:@"@max.date"];
@min用來(lái)查找集合中right keyPath指定的屬性的最小值铛嘱。
NSDate*earliestDate = [self.transactions valueForKeyPath:@"@min.date"];
備注:@max和@min在進(jìn)行判斷時(shí)暖释,都是通過(guò)調(diào)用compare:方法進(jìn)行判斷袭厂,所以可以通過(guò)重寫該方法對(duì)判斷過(guò)程進(jìn)行控制墨吓。
數(shù)組操作符
@unionOfObjects將集合對(duì)象中,所有payee對(duì)象放在一個(gè)數(shù)組中并返回纹磺。
NSArray*payees = [self.transactions valueForKeyPath:@"@unionOfObjects.payee"];
@distinctUnionOfObjects將集合對(duì)象中帖烘,所有payee對(duì)象放在一個(gè)數(shù)組中,并將數(shù)組進(jìn)行去重后返回橄杨。
NSArray*distinctPayees = [self.transactions valueForKeyPath:@"@distinctUnionOfObjects.payee"];
注意:以上兩個(gè)方法中秘症,如果操作的屬性為nil照卦,在添加到數(shù)組中時(shí)會(huì)導(dǎo)致Crash。
嵌套操作符
由于嵌套操作符是需要對(duì)嵌套的集合對(duì)象進(jìn)行操作乡摹,所以新建一個(gè)arrayOfArrays對(duì)象役耕,其中包含兩個(gè)數(shù)組,數(shù)組中存儲(chǔ)的都是Transaction類型對(duì)象聪廉。
NSArray*moreTransactions = ....;NSArray*arrayOfArrays = @[self.transactions, moreTransactions];
@unionOfArrays是用來(lái)操作集合內(nèi)部的集合對(duì)象瞬痘,將所有right keyPath對(duì)應(yīng)的對(duì)象放在一個(gè)數(shù)組中返回。
NSArray*collectedPayees = [arrayOfArrays valueForKeyPath:@"@unionOfArrays.payee"];
@distinctUnionOfArrays是用來(lái)操作集合內(nèi)部的集合對(duì)象板熊,將所有right keyPath對(duì)應(yīng)的對(duì)象放在一個(gè)數(shù)組中框全,并進(jìn)行排重。
NSArray*collectedDistinctPayees = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.payee"];
@distinctUnionOfSets是用來(lái)操作集合內(nèi)部的集合對(duì)象干签,將所有right keyPath對(duì)應(yīng)的對(duì)象放在一個(gè)set中津辩,并進(jìn)行排重。
NSSet*collectedPayees = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfSets.payee"];
小技巧
如果在集合對(duì)象中操作的屬性容劳,本來(lái)就是NSNumber類型喘沿,則可以像下面這樣,直接用self代表值自身鸭蛙。
NSArray *array= @[@(productA.price), @(productB.price), @(productC.price), @(productD.price)];NSNumber *avg = [arrayvalueForKeyPath:@"@avg.self"];
屬性驗(yàn)證
在調(diào)用KVC時(shí)可以先進(jìn)行驗(yàn)證摹恨,驗(yàn)證通過(guò)下面兩個(gè)方法進(jìn)行,支持key和keyPath兩種方式娶视。驗(yàn)證方法默認(rèn)實(shí)現(xiàn)返回YES晒哄,可以通過(guò)重寫對(duì)應(yīng)的方法修改驗(yàn)證邏輯。
驗(yàn)證方法需要我們手動(dòng)調(diào)用肪获,并不會(huì)在進(jìn)行KVC的過(guò)程中自動(dòng)調(diào)用寝凌。
- (BOOL)validateValue:(inoutid_Nullable * _Nonnull)ioValue forKey:(NSString*)inKey error:(outNSError**)outError;- (BOOL)validateValue:(inoutid_Nullable * _Nonnull)ioValue forKeyPath:(NSString*)inKeyPath error:(outNSError**)outError;
下面是使用驗(yàn)證方法的例子。在validateValue方法的內(nèi)部實(shí)現(xiàn)中孝赫,如果傳入的value或key有問(wèn)題较木,可以通過(guò)返回NO來(lái)表示錯(cuò)誤,并設(shè)置NSError對(duì)象青柄。
Person *person = [[Person alloc] init];NSError*error;NSString*name =@"John";if(![person validateValue:&name forKey:@"name"error:&error]) {NSLog(@"%@", error);}
單獨(dú)驗(yàn)證
KVC還支持對(duì)單獨(dú)屬性做驗(yàn)證伐债,可以通過(guò)定義validate<Key>:error:格式的方法,并在方法內(nèi)部實(shí)現(xiàn)驗(yàn)證代碼致开。在編寫KVC驗(yàn)證代碼的時(shí)候峰锁,應(yīng)該先查找屬性有沒(méi)有自定義validate方法,然后再查找validateValue:方法双戳,如果有則調(diào)用自己實(shí)現(xiàn)的方法虹蒋,如果兩個(gè)方法都沒(méi)有實(shí)現(xiàn)則默認(rèn)返回YES。
- (BOOL)validateName:(id*)ioValue error:(NSError* __autoreleasing *)outError{if((*ioValue ==nil) || ([(NSString*)*ioValue length] <2)) {if(outError !=NULL) {? ? ? ? ? ? *outError = [NSErrorerrorWithDomain:PersonErrorDomain? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? code:PersonInvalidNameCode? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? userInfo:@{NSLocalizedDescriptionKey:@"Name too short"}];? ? ? ? }returnNO;? ? }returnYES;}
我覺(jué)得KVC應(yīng)該支持validateValue自動(dòng)驗(yàn)證,在調(diào)用setValue或getValue時(shí)自動(dòng)進(jìn)行驗(yàn)證魄衅,如果不符合驗(yàn)證規(guī)則峭竣,就調(diào)用失敗。如果外界使用的地方都先調(diào)用一次validateValue的話晃虫,這是很麻煩的皆撩。當(dāng)然也有解決方法,可以通過(guò)Method Swizzling方法hook住setValue和getValue方法哲银。
NSMutableArray搜索模式
這是mutableArrayValueForKey:的默認(rèn)實(shí)現(xiàn)毅访,給一個(gè)key當(dāng)做輸入?yún)?shù)。在接收訪問(wèn)器調(diào)用的對(duì)象中盘榨,返回一個(gè)名為key的可變代理數(shù)組喻粹,這個(gè)代理數(shù)組就是用來(lái)響應(yīng)外界KVO的對(duì)象,通過(guò)下面的步驟進(jìn)行查找:
查找一對(duì)方法insertObject:in<Key>AtIndex:和removeObjectFrom<Key>AtIndex:(相當(dāng)于NSMutableArray的原始方法insertObject:atIndex:和removeObjectAtIndex:)或者方法名是insert<Key>:atIndexes:和remove<Key>AtIndexes:(相當(dāng)于NSMutableArray的原始方法insertObjects:atIndexes:和removeObjectsAtIndexes:)草巡。
如果找到最少一個(gè)insert方法和最少一個(gè)remove方法守呜,則返回一個(gè)代理對(duì)象,來(lái)響應(yīng)發(fā)送給NSMutableArray的組合消息insertObject:in<Key>AtIndex:山憨、removeObjectFrom<Key>AtIndex:查乒、insert<Key>:atIndexes:,和remove<Key>AtIndexes:消息郁竟。
當(dāng)對(duì)象接收一個(gè)mutableArrayValueForKey:消息并實(shí)現(xiàn)可選替換方法玛迄,例如replaceObjectIn<Key>AtIndex:withObject:或replace<Key>AtIndexes:with<Key>:方法,代理對(duì)象會(huì)在適當(dāng)?shù)那闆r下使用它們棚亩,以獲得最佳性能蓖议。
如果對(duì)象沒(méi)有可變數(shù)組方法,查找一個(gè)替代方法讥蟆,命名格式為set<Key>:勒虾。在這種情況下,向mutableArrayValueForKey:的原始響應(yīng)者發(fā)送一個(gè)set<Key>:消息瘸彤,來(lái)返回一個(gè)代理對(duì)象來(lái)響應(yīng)NSMutableArray事件修然。
提示:
這一步描述的機(jī)制遠(yuǎn)不如上一步有效,因?yàn)樗赡苤貜?fù)創(chuàng)建新的集合對(duì)象质况,而不是修改現(xiàn)有的對(duì)象愕宋。因此,在自己設(shè)計(jì)的KVC時(shí)應(yīng)該盡量避免它结榄。
如果沒(méi)有可變數(shù)組的方法中贝,也沒(méi)有找到訪問(wèn)器,但接受響應(yīng)的類accessInstanceVariablesDirectly屬性返回YES潭陪,則查找一個(gè)名為_<key>或<key>的實(shí)例變量雄妥。
按照這個(gè)順序,如果找到實(shí)例變量依溯,則返回一個(gè)代理對(duì)象老厌。改對(duì)象將接收所有NSMutableArray發(fā)送過(guò)來(lái)的消息,通常是NSMutableArray或其子類黎炉。
如果所有情況都失敗枝秤,則返回一個(gè)可變的集合代理對(duì)象。當(dāng)它接收NSMutableArray消息時(shí)慷嗜,發(fā)送一個(gè)setValue:forUndefinedKey:消息給接收mutableArrayValueForKey:消息的原始對(duì)象淀弹。
這個(gè)setValue:forUndefinedKey:的默認(rèn)實(shí)現(xiàn)是提出一個(gè)NSUndefinedKeyException異常,但是子類可以重寫這個(gè)實(shí)現(xiàn)庆械。
作者:劉小壯
鏈接:http://www.reibang.com/p/1d39bc610a5b
來(lái)源:簡(jiǎn)書
簡(jiǎn)書著作權(quán)歸作者所有薇溃,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處。