KVC之使用Collection Operators(集合運算符)

當向支持KVC的對象發(fā)送valueForKeyPath:消息時,你可以在key path中嵌入一個 Collection Operators(集合運算符)。 Collection Operators是一個以at符號(@)開頭的關(guān)鍵字,它指定了一個操作: getter應(yīng)該在返回之前以某種方式對數(shù)據(jù)進行操作。NSObjectvalueForKeyPath:方法的默認實現(xiàn)已經(jīng)實現(xiàn)了這種行為。

key path包含集合運算符時,運算符之前的key path的部分被稱為left key path, 表示 receiver在其上操作的集合。如果直接向集合對象(比如NSArray)發(fā)送消息,left key path可能會被忽略。

在運算符后面的key path部分稱為right key path,指定了運算符應(yīng)該處理的集合中的屬性。 所有的集合運算符(除了@count)都需要有right key path。下圖說明了運算符的key path格式。

集合運算符

集合運算符含有三種基本類型:

  • Aggregation Operators(聚合運算符): 以某種方式合并集合中的對象,并返回一個通常與right key path指定的屬性的數(shù)據(jù)類型相匹配的對象。(@count運算符除外,它不需要right key path,并且始終返回一個NSNumber實例)

  • Array Operators(數(shù)組運算符):返回一個數(shù)組實例,該實例包含指定集合中的一些對象子集。

  • Nesting Operators(嵌套運算符):處理包含其他集合的集合(就是一個嵌套array或者嵌套set),并返回一個NSArray或者NSSet實例,具體取決于運算符,它以某種方式組合了嵌套集合的屬性。


看到這些還是不太懂運算符的作用,別慌,通過幾個例子來看一下各個運算符的作用

樣本數(shù)據(jù)

Listing 4-1:

@interface BankAccount : NSObject
@property (nonatomic) NSNumber* currentBalance;              // An attribute
@property (nonatomic) Person* owner;                         // A to-one relation
@property (nonatomic) NSArray< Transaction* >* transactions; // A to-many relation
@end
@interface Transaction : NSObject
@property (nonatomic) NSString* payee;   // To whom
@property (nonatomic) NSNumber* amount;  // How much
@property (nonatomic) NSDate* date;      // When
@end

BankAccount實例有一個transactions數(shù)組,該數(shù)組存放的是Transaction對象,假設(shè)該數(shù)組填充了Table 4-1的數(shù)據(jù)

Table: 4-1:

payee values amount values date values
Green Power $120.00 Dec 1, 2015
Green Power $150.00 Jan 1, 2016
Green Power $170.00 Feb 1, 2016
Car Loan $250.00 Jan 15, 2016
Car Loan $250.00 Feb 15, 2016
Car Loan $250.00 Mar 15, 2016
General Cable $120.00 Dec 1, 2015
General Cable $155.00 Jan 1, 2016
General Cable $120.00 Feb 1, 2016
Mortgage $1,250.00 Jan 15, 2016
Mortgage $1,250.00 Feb 15, 2016
Mortgage $1,250.00 Mar 15, 2016
Animal Hospital $600.00 Jul 15, 2016

Aggregation Operators (聚合運算符)

Aggregation operators(聚合運算符)可以處理array或者set屬性,產(chǎn)生一個反映集合某些方面的單個值。

@avg

當你指定@avg運算符時,valueForKeyPath:會為集合中每個元素讀取由right key path指定的屬性,將其轉(zhuǎn)化為double類型(將nil轉(zhuǎn)為0),并計算這些元素的算術(shù)平均值。并返回結(jié)果存儲在NSNumber的實例

獲取表4-1中樣本數(shù)據(jù)的平均交易額

NSNumber *transactionAverage = [self.transactions valueForKeyPath:@"@avg.amount"];

上面的方法中,valueForKeyPath:會讀取集合(這里是transactions數(shù)組)中每個元素(這里是Transaction對象)中right key path指定的屬性(這里是amount),將該屬性的value轉(zhuǎn)為double類型,并計算它們的算術(shù)平均數(shù),將平均數(shù)轉(zhuǎn)為NSNumber返回

transactionAverage的結(jié)果是456.54

@count

當你指定@count運算符時,valueForKeyPath:會返回集合中對象的數(shù)量(NSNumber實例),right key path(如果存在)會被忽略

獲取transactionsTransaction對象的數(shù)量

NSNumber *numberOfTransactions = [self.transactions valueForKeyPath:@"@count"];

numberOfTransactions的值是13;
可以看到@count是不需要right key path

@max

當你指定@max運算符,valueForKeyPath:將在由right key path指定的集合條目中進行搜索并返回最大值。搜索使用compare:方法進行比較。因此,由right key path指定的屬性必須能夠?qū)υ摲椒ㄟM行有效的響應(yīng)。該搜索忽略集合中的nil值。

進行以下操作獲取表4-1中transactions中的最大日期值

NSDate *latestDate = [self.transactions valueForKeyPath:@"@max.date"];

latestDate的值是 Jul 15, 2016.

注意: right key path指定的屬性必須能響應(yīng)compare:方法,否則會拋出NSInvalidArgumentException異常

比如在Transaction中添加一個owner屬性,owner是一個Person對象(繼承自NSObject),不能響應(yīng)compare:方法。調(diào)用下面方法
[self.transactions valueForKeyPath:@"@max.owner"];

就會拋出異常:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person compare:]: unrecognized selector sent to instance 0x604000002750'

從reason中也可以看出valueForKeyPath:方法確實是調(diào)用的compare :

@min

當你指定@min運算符的時候,valueForKeyPath:將在由right key path指定的集合條目中進行查詢并返回最小值。該查詢使用compare:方法進行比較。因此,right key path指定的屬性必須能夠?qū)Υ朔椒ㄟM行有意義響應(yīng)。搜索將忽略集合中的nil值。(跟@max很相似)

獲取表4-1列出的交易中日期值(最早交易日期)的最小值:

NSDate *earliestDate = [self.transactions valueForKeyPath:@"@min.date"];

earliestDate的值是Dec 1, 2015.

@sum

當你指定@sum運算符時,valueForKeyPath:為集合中的每一個元素讀取由right key path指定的屬性,并轉(zhuǎn)為double類型(nil值用0替代),并計算它們的和。并返回該結(jié)果(將返回值存儲在NSNumber中返回)
獲取表4-1中樣本數(shù)據(jù)中交易金額的總和:

NSNumber *amountSum = [self.transactions valueForKeyPath:@"@sum.amount"];

amountSum的值是5,935.00。

提示:當集合中存放的是NSNumber時,可以直接把self作為right key path,比如

[@[@(1), @(2), @(3)] valueForKeyPath:@"@max.self"]

返回的是@3

想一下,當right key pathself時會發(fā)生什么?

right key path指定的對象是要進行Operators所指定的操作的,以@max為例,當執(zhí)行下面的方法時,

[self.transactions valueForKeyPath:@"@max.self"]

通過上面我們知道right key path指定的屬性對象會調(diào)用compare:方法,而現(xiàn)在right key pathself,也就是指定的是Transaction實例對象,而Transaction是沒有compare:方法的,所以會拋出錯誤:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Transaction compare:]: unrecognized selector sent to instance 0x600000252e40'

但如果是下面這樣就不會出錯了

 [@[@(1), @(2), @(3)] valueForKeyPath:@"@max.self"]

因為數(shù)組中存放的是NSNumber,當right key pathself時,是數(shù)組中的NSNumber進行比較,最后返回最大值@3。 其他的運算符類似。

Array Operators(數(shù)組運算符)

數(shù)組運算符使valueForKeyPath:返回對應(yīng)于right key path指定的一組特定對象的對象數(shù)組。 即數(shù)組運算符返回的是一個數(shù)組,數(shù)組中存放的是right key path指定的對象屬性。

注意:當使用數(shù)組運算符時,如果leaf objects(葉子對象)為nil,則valueForKeyPath:方法會拋出異常

@distinctUnionOfObjects

當你指定了@distinctUnionOfObjects運算符,valueForKeyPath:創(chuàng)建并返回一個數(shù)組,該數(shù)組包含與right key path指定的屬性相對應(yīng)的集合中不同對象??梢岳斫鉃閿?shù)組中right key path指定的屬性組成一個新的數(shù)組,并對該數(shù)組中重復(fù)的數(shù)據(jù)進行移除,最后返回該數(shù)組。

NSArray *distinctPayees = [self.transactions valueForKeyPath:@"@distinctUnionOfObjects.payee"];

獲得一個payee屬性值的數(shù)組集合,transactions數(shù)組中的Transaction對象的payee屬性的值如果有重復(fù)的將會被忽略。

可以理解為將transactions數(shù)組中的所有Transaction對象的payee屬性值存放到一個新的數(shù)組中,將新數(shù)組中重復(fù)的值移除并返回該數(shù)組。

distinctPayees數(shù)組包含以下字符串:Car Loan, General Cable, Animal Hospital, Green Power, Mortgage.

注意:
@unionOfObjects運算符的作用與其類似,但是沒有進行去重。

@unionOfObjects

當你指定@unionOfObjects運算符時,valueForKeyPath:創(chuàng)建并返回一個數(shù)組,該數(shù)組包含所有的集合(right key path指定的屬性所組成的集合)對象。跟@ distinctUnionOfObjects非常類似,區(qū)別就是重復(fù)的對象不會移除。

獲取在transactions中的Transaction對象的 payee屬性的值的集合

NSArray *payees = [self.transactions valueForKeyPath:@"@unionOfObjects.payee"];

payees數(shù)組包含以下字符串:Green Power, Green Power, Green Power, Car Loan, Car Loan, Car Loan, General Cable, General Cable, General Cable, Mortgage, Mortgage, Mortgage, Animal Hospital.

從上面的例子中可以看出Array Operators數(shù)組運算符返回的結(jié)果是數(shù)組,該數(shù)組存放的數(shù)據(jù)是right key path指定的屬性。

  • right key pathself時,其效果就是把數(shù)組中的數(shù)據(jù)進行去重處理(distinct)或者不去重。

  • 因為@distinctUnionOfObjects會對數(shù)據(jù)進行去重,所以新返回的數(shù)組里的數(shù)據(jù)的前后順序跟原來數(shù)組的順序可能會不一樣,而@unionOfObjects不去重,所以返回的數(shù)據(jù)跟原來數(shù)組里的數(shù)據(jù)順序一致。

    比如:

      NSArray *array = @[@"blue",@"blue",@"blue",@"blue",@"red",@"yellow",@"purple"];
      NSArray *result = [array valueForKeyPath:@"@distinctUnionOfObjects.self"];
    

    打印為: yellow, purple, blue, red, 而不是blue, red, yellow, purple,

    NSArray *result = [array valueForKeyPath:@"@unionOfObjects.self"];
    

    這里打印 blue, blue, blue, red, yellow,purple,跟數(shù)組中原有的順序一致。

前面說過,當使用數(shù)組運算符時,如果leaf objects(葉子對象)為nil,則valueForKeyPath:方法會拋出異常

比如

id objects = @[@{@"color": @"blue"},
               @{@"color": @"red"},
               @{@"color": @"green"},
               @"notacolor"];

[objects valueForKeyPath:@"@unionOfObjects.color"];

則會拋出異常

Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFConstantString 0x10aa4d470> valueForUndefinedKey:]: this class is not key value coding-compliant for the key

Nesting Operators(嵌套運算符)

嵌套運算符對嵌套集合(集合中的每個條目本身又包含了一個集合)進行操作,

當使用嵌套運算符時,如果leaf objects (葉子對象)nil,valueForKeyPath:方法會拋出一個異常。

NSArray* moreTransactions = @[<# transaction data #>];
NSArray* arrayOfArrays = @[self.transactions, moreTransactions];

moreTransactions數(shù)組中的數(shù)據(jù):

payee values amount values date values
General Cable - Cottage $120.00 Dec 18, 2015
General Cable - Cottage $155.00 Jan 9, 2016
General Cable - Cottage $120.00 Dec 1, 2016
Second Mortgage $1,250.00 Nov 15, 2016
Second Mortgage $1,250.00 Sep 20, 2016
Second Mortgage $1,250.00 Feb 12, 2016
Hobby Shop $600.00 Jun 14, 2016

@distinctUnionOfArrays

當你指定@distinctUnionOfArrays運算符時,valueForKeyPath:創(chuàng)建并返回一個數(shù)組,該數(shù)組包含與right key path指定的屬性對應(yīng)的所有集合的組合的不同對象。與數(shù)組運算符@distinctUnionOfObjects很相似。

arrayOfArrays中的數(shù)組獲取屬性payee的不同的值。

NSArray *collectedDistinctPayees = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.payee"];

collectedDistinctPayees數(shù)組包含以下值: Hobby Shop, Mortgage, Animal Hospital, Second Mortgage, Car Loan, General Cable - Cottage, General Cable, Green Power.

@unionOfArrays運算符與該運算符作用相似,只不過沒有進行去重

@unionOfArrays

當指定@unionOfArrays運算符時,valueForKeyPath:創(chuàng)建并返回一個數(shù)組,
該數(shù)組包含所有的right key path指定的屬性。與@distinctUnionOfArrays類似,只不過不進行去重。

獲取arrayOfArrays數(shù)組中所有的對象的payee屬性的值。

NSArray *collectedPayees = [arrayOfArrays valueForKeyPath:@"@unionOfArrays.payee"];

collectedPayees數(shù)組的包含的數(shù)據(jù)有:Green Power, Green Power, Green Power, Car Loan, Car Loan, Car Loan, General Cable, General Cable, General Cable, Mortgage, Mortgage, Mortgage, Animal Hospital, General Cable - Cottage, General Cable - Cottage, General Cable - Cottage, Second Mortgage, Second Mortgage, Second Mortgage, Hobby Shop.

@distinctUnionOfSets

當指定@distinctUnionOfSets運算符時,valueForKeyPath:創(chuàng)建并返回一個NSSet對象,該對象包含right key path指定的屬性組成的集合的所有不同對象。

這個運算符跟@distinctUnionOfArrays很類似,只不過它作用于包含NSSetNSSet,而不是包含數(shù)組的數(shù)組, 并且返回的是NSSet而不是數(shù)組。

另外,想當然的會聯(lián)想到既然有了@distinctUnionOfSets運算符,是不是跟上面的一樣會有@unionOfSets運算符? 對于NSSet來說是沒有@unionOfSets運算符的,如果使用的話會拋出'NSInvalidArgumentException'異常,提示NSSet沒有實現(xiàn)unionOfSets運算符。

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[<__NSSetI 0x60400025c530> valueForKeyPath:]: this class does not implement the unionOfSets operation.

left key path

前面對left key path沒有細講,這里對其簡單介紹下,大致如下:

[object valueForKeyPath:  @"keypathToCollection.@collectionOperator.keypathToProperty"]

等效于

[[object valueForKeyPath:@"keyPathToCollection"] 
 valueForKeyPath:@"@collectionOperator.keypathToProperty"]

也等效于

[[[object valueForKeyPath:@"keyPathToCollection"] 
  valueForKeyPath:@"keypathToProperty"]
 performTheCollectionOperator]

舉個例子,正如前面說到的@avg運算符,

NSNumber *avg = [self.transactions valueForKeyPath:@"@avg.amount"];

這里的self是指BankAccount類的實例,transactionsBankAccount的數(shù)組屬性,我們也可以這樣

NSNumber *avg = [self valueForKeyPath:@"transactions.@avg.amount"];

兩者是等效的,當然left key path既然是一個key path,當然也可以這樣

 NSNumber *avg = [self valueForKeyPath:@"owner.numbers.@avg.self"];

這里的ownerPerson實例,numbersPerson的一個數(shù)組,里面存放的是@1,@3,@5,由于這里存放的是NSNumber實例,所以right key path寫成self。運行的是結(jié)果為

3

自定義集合運算符

上面介紹了一些運算符的用法,我們很自然的想要自定義運算符,不過我翻了一下蘋果文檔,并沒有找到關(guān)于自定義集合運算符的相關(guān)介紹,倒是在NSHipster看到Mattt 大神于2012年的一篇文章中提到蘋果文檔曾這么說過:

Note: It is not currently possible to define your own collection operators.

雖然蘋果沒有提供自定義集合運算符的文檔,但是這篇文章還是提供了一些思路:

swizzles valueForKeyPath:來解析自定義的DSL,來實現(xiàn)自定義的效果。

不過我在Github上找到了一個關(guān)于自定義集合運算符的demo,里面自定義了諸如unionOfPresentObjects(跟@unionOfObjects類似,但忽略 不兼容/ nil / NSNull 的對象),@standardDeviation,@"@reverse"等自定義運算符,感興趣的可以點擊這里到Github查看

根據(jù)這個demo發(fā)現(xiàn),我們?yōu)橐粋€數(shù)組添加一個<collectionOperator>ForKeyPath:_<collectionOperator>ForKeyPath:的方法,然后我們就可以通過像使用集合運算符的方法使用該方法,比如:

@interface NSArray (CollectionOperator)
- (id)myCustomOperatorForKeyPath:(NSString *)keyPath;
@end

@implementation NSArray (CollectionOperator)
- (id)myCustomOperatorForKeyPath:(NSString *)keyPath
{
    NSLog(@"myCustomOperatorForKeyPath : %@", keyPath);
    return nil;
}
@end

使用

 [self.positions valueForKeyPath:@"@myCustomOperator.self"];

打印信息為:

myCustomOperatorForKeyPath : self

然后我們就可以在我們自定義的方法里做一些我們想要的操作。

參考

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

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

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