收集运算符

集合运算符可用于 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"];