收集運算子

集合運算子可用於 KVC 金鑰路徑,以對集合型別屬性(即 NSArrayNSSet 等)執行操作。例如,要執行的常見操作是計算集合中的物件。要實現此目的,請使用 @count 集合運算子

self.array = @[@5, @4, @3, @2, @1];
NSNumber *count = [self.array valueForKeyPath:@"@count"];
NSNumber *countAlt = [self valueForKeyPath:@"array.@count"];
// count == countAlt == 5

雖然這是完全多餘的,這裡(我們可以只訪問 count 屬性),它可以是偶爾有用,雖然它很少是必要的。然而,有一些更有用的收集運算元,即 @max@min@sum@avg@unionOf 系列。需要注意的是這些運算子是非常重要的需要一個單獨的關鍵路徑如下操作才能正常工作。以下是它們的列表以及它們使用的資料型別:

操作者 資料型別
@count (沒有)
@max NSNumberNSDateint(及相關)等
@min NSNumberNSDateint(及相關)等
@sum NSNumberint(及相關),double(及相關)等
@avg NSNumberint(及相關),double(及相關)等
@unionOfObjects NSArrayNSSet
@distinctUnionOfObjects NSArrayNSSet
@unionOfArrays NSArray<NSArray*>
@distinctUnionOfArrays NSArray<NSArray*>
@distinctUnionOfSets NSSet<NSSet*>

@max@min 將分別返回集合中物件屬性的最高值或最低值。例如,檢視以下程式碼:

// `Point` class used in our collection
@interface Point : NSObject

@property NSInteger x, y;

+ (instancetype)pointWithX:(NSInteger)x y:(NSInteger)y;

@end

...

self.points = @[[Point pointWithX:0 y:0],
                [Point pointWithX:1 y:-1],
                [Point pointWithX:5 y:-6],
                [Point pointWithX:3 y:0],
                [Point pointWithX:8 y:-4],
];

NSNumber *maxX = [self valueForKeyPath:@"points.@max.x"];
NSNumber *minX = [self valueForKeyPath:@"points.@min.x"];
NSNumber *maxY = [self valueForKeyPath:@"points.@max.y"];
NSNumber *minY = [self valueForKeyPath:@"points.@min.y"];

NSArray<NSNumber*> *boundsOfAllPoints = @[maxX, minX, maxY, minY];

...

在僅僅 4 行程式碼和純基礎中,憑藉 Key-Value Coding 集合運算子的強大功能,我們能夠提取一個封裝陣列中所有點的矩形。

重要的是要注意這些比較是通過在物件上呼叫 compare:方法進行的,因此如果你想讓自己的類與這些運算子相容,則必須實現此方法。

正如你可能猜到的那樣,@sum 將新增屬性的所有值。

@interface Expense : NSObject

@property NSNumber *price;

+ (instancetype)expenseWithPrice:(NSNumber *)price;

@end

...

self.expenses = @[[Expense expenseWithPrice:@1.50],
                  [Expense expenseWithPrice:@9.99],
                  [Expense expenseWithPrice:@2.78],
                  [Expense expenseWithPrice:@9.99],
                  [Expense expenseWithPrice:@24.95]
];

NSNumber *totalExpenses = [self valueForKeyPath:@"expenses.@sum.price"];

在這裡,我們使用 @sum 來查詢陣列中所有費用的總價。如果我們想要找到我們為每筆費用支付的平均價格,我們可以使用 @avg

NSNumber *averagePrice = [self valueForKeyPath:@"expenses.@avg.price"];

最後,還有 @unionOf 家族。這個系列中有五個不同的運算子,但它們的工作方式大致相同,每個運算子之間的差異很小。首先,@unionOfObjects 將返回陣列中物件屬性的陣列:

// See "expenses" array above

NSArray<NSNumber*> *allPrices = [self valueForKeyPath:
    @"expenses.@unionOfObjects.price"];

// Equal to @[ @1.50, @9.99, @2.78, @9.99, @24.95 ]

@distinctUnionOfObjects 的功能與 @unionOfObjects 相同,但它會刪除重複項:

NSArray<NSNumber*> *differentPrices = [self valueForKeyPath:
    @"expenses.@distinctUnionOfObjects.price"];

// Equal to @[ @1.50, @9.99, @2.78, @24.95 ]

最後,@unionOf 系列中的最後 3 個運算子將更深入一步並返回為雙重巢狀陣列中包含的屬性找到的值陣列:

NSArray<NSArray<Expense*,Expense*>*> *arrayOfArrays =
    @[
        @[ [Expense expenseWithPrice:@19.99],
           [Expense expenseWithPrice:@14.95],
           [Expense expenseWithPrice:@4.50],
           [Expense expenseWithPrice:@19.99]
         ],

        @[ [Expense expenseWithPrice:@3.75],
           [Expense expenseWithPrice:@14.95]
         ]
     ];

// @unionOfArrays
NSArray<NSNumber*> allPrices = [arrayOfArrays valueForKeyPath:
    @"@unionOfArrays.price"];
// Equal to @[ @19.99, @14.95, @4.50, @19.99, @3.75, @14.95 ];

// @distinctUnionOfArrays
NSArray<NSNumber*> allPrices = [arrayOfArrays valueForKeyPath:
    @"@distinctUnionOfArrays.price"];
// Equal to @[ @19.99, @14.95, @4.50, @3.75 ];

這個例子中缺少的是 @distinctUnionOfSets,但是它的功能與 @distinctUnionOfArrays 完全相同,但是使用並返回 NSSets(沒有非 distinct 版本,因為在一個集合中,每個物件必須是不同的)。

就是這樣! 如果使用正確,集合運算子可以非常強大,並且可以幫助避免不必要地迴圈遍歷內容。

最後一點:你還可以在 NSNumbers 的陣列上使用標準集合運算子(無需額外的屬性訪問)。為此,你可以訪問僅返回物件的 self 偽屬性:

NSArray<NSNumber*> *numbers = @[@0, @1, @5, @27, @1337, @2048];

NSNumber *largest = [numbers valueForKeyPath:@"@max.self"];
NSNumber *smallest = [numbers valueForKeyPath:@"@min.self"];
NSNumber *total = [numbers valueForKeyPath:@"@sum.self"];
NSNumber *average = [numbers valueForKeyPath:@"@avg.self"];