KVC 集合运算符

119 阅读3分钟

参考链接

实际上,一门语言是否优雅归结起来就是其怎么样能更好的避免循环。

引子

给出列表中所有员工的平均薪酬

double totalSalary = 0.0;
for (Employee *employee in employees) {
  totalSalary += [employee.salary doubleValue];
}
double averageSalary = totalSalary / [employees count];

示例的做法正确且常见,唯一的问题是不简洁,不优雅。

键值编码的方式是

[employees valueForKeyPath:@"@avg.salary"];

关于集合运算符

OC允许在valueForKeyPath:方法中使用 key path 符号在一个集合中执行方法。 只要在 key path 中看见了@,它都代表了一个特定的集合方法,其结果可以被返回或者链接,就像其他的 key path 一样。

简单的集合运算符

返回的是 strings, number, 或者 dates


//键-值 编码会在必要的时候把基本数据类型的数据自动装箱和拆箱到
//`NSNumber`或者`NSValue`中来确保一切工作正常。


@interface Product : NSObject
@property NSString *name;
@property double price;
@property NSDate *launchedOn;
@end

@implementation Product
+(instancetype)createWithName:(NSString*)name
                        price:(double)price 
                    launcheON:(NSDate*)launchedOn{

    Product *product = [[Product alloc]init];

    product.name = name;

    product.price = price;

    product.launchedOn = launchedOn;

    return product;

}

@end

示例数组


[Product createWithName:@"iPhone 5"
                  price:199
              launcheON:[NSDate dateWithTimeIntervalSince1970:100]];

[Product createWithName:@"iPad Mini"
                  price:329
              launcheON:[NSDate dateWithTimeIntervalSince1970:101]];

[Product createWithName:@"MacBook Pro"
                  price:1699
              launcheON:[NSDate dateWithTimeIntervalSince1970:102]];

[Product createWithName:@"iMac"
                  price:1299
              launcheON:[NSDate dateWithTimeIntervalSince1970:103]];
  • @count: 返回一个值为集合中对象总数的NSNumber对象。

  • @sum : 首先把集合中的每个对象都转换为double类型,然后计算其总,最后返回一个值为这个总和的NSNumber对象。

  • @avg: 把集合中的每个对象都转换为double类型,返回一个值为平均值的NSNumber对象。

  • @max: 使用compare:方法来确定最大值。所以为了让其正常工作,集合中所有的对象都必须支持和另一个对象的比较。

  • @min: 和@max一样,但是返回的是集合中的最小值。

[products valueForKeyPath:@"@count"]; // 4
[products valueForKeyPath:@"@sum.price"]; // 3526.00
[products valueForKeyPath:@"@avg.price"]; // 881.50
[products valueForKeyPath:@"@max.price"]; // 1699.00
[products valueForKeyPath:@"@min.launchedOn"]; // Thu Jan  1 08:01:40 1970

也可以简单的通过把 self 作为操作符后面的 key path 来获取一个由NSNumber组成的数组或者集合的总值。

NSArray *arr = @[@1, @2, @3]; 
[arr valueForKeyPath:@"@count.self"]
[arr valueForKeyPath:@"@sum.self"]
[arr valueForKeyPath:@"@avg.self"]
[arr valueForKeyPath:@"@max.self"]
[arr valueForKeyPath:@"@min.self"]

对象操作符

返回的是一个数组

示例数组

[Product createWithName:@"iPhone 5"
                  price:199
              launcheON:[NSDate dateWithTimeIntervalSince1970:100]];
              
[Product createWithName:@"iPhone 5"
                  price:199
              launcheON:[NSDate dateWithTimeIntervalSince1970:100]];

[Product createWithName:@"iPad Mini"
                  price:329
              launcheON:[NSDate dateWithTimeIntervalSince1970:101]];

[Product createWithName:@"MacBook Pro"
                  price:1699
              launcheON:[NSDate dateWithTimeIntervalSince1970:102]];

[Product createWithName:@"iMac"
                  price:1299
              launcheON:[NSDate dateWithTimeIntervalSince1970:103]];
              
[Product createWithName:@"iMac"
                  price:1299
              launcheON:[NSDate dateWithTimeIntervalSince1970:103]];

@unionOfObjects / @distinctUnionOfObjects: 返回一个由操作符右边的 key path 所指定的对象属性组成的数组。其中@distinctUnionOfObjects 会对数组去重, 而 unionOfObjects 不会。

[products valueForKeyPath:@"@unionOfObjects.name"];

// "iPhone 5" "iPhone 5" "iPad Mini" "MacBook Pro" "iMac" "iMac"

[products valueForKeyPath:@"@distinctUnionOfObjects.name"];

// "iMac" "iPad Mini" "MacBook Pro" "iPhone 5"

数组和集合运算符

数组和集合操作符跟对象操作符很相似,只不过它是在NSArrayNSSet所组成的集合中工作的。

  • @distinctUnionOfArrays / @unionOfArrays: 返回了一个数组,其中包含这个集合中每个数组对于这个操作符右面指定的 key path 进行操作之后的值。正如你期望的,distinct版本会移除重复的值。

  • @distinctUnionOfSets: 和@distinctUnionOfArrays差不多, 但是它期望的是一个包含着NSSet对象的NSSet,并且会返回一个NSSet对象。因为集合不能包含重复的值,所以它只有distinct操作。

NSArray *products1 = @[
        [Product createWithName:@"iPhone 5"
                          price:199
                      launcheON:[NSDate dateWithTimeIntervalSince1970:100]],
        [Product createWithName:@"iPhone 5"
                          price:199
                      launcheON:[NSDate dateWithTimeIntervalSince1970:100]],
        [Product createWithName:@"iPad Mini"
                          price:329
                      launcheON:[NSDate dateWithTimeIntervalSince1970:101]],
        [Product createWithName:@"MacBook Pro"
                          price:1699
                      launcheON:[NSDate dateWithTimeIntervalSince1970:102]],
        [Product createWithName:@"iMac"
                          price:1299
                      launcheON:[NSDate dateWithTimeIntervalSince1970:103]],
        [Product createWithName:@"iMac"
                          price:1299
                      launcheON:[NSDate dateWithTimeIntervalSince1970:103]],
    ];

NSArray *products2 = @[
        [Product createWithName:@"iPhone 13"
                          price:199
                      launcheON:[NSDate dateWithTimeIntervalSince1970:100]]
    ];
[@[products1,products2] valueForKeyPath:@"@distinctUnionOfArrays.name"]

//"iPad Mini" "iMac" "iPhone 13" "MacBook Pro" "iPhone 5"