你知道的不一樣的KVC鍵值編碼
像Ruby,JavaScript,Python等愛好者經常會嘲笑Objective-C那臃腫的語法。
實際上,一門語言是否優雅歸結起來就是其怎麼樣能更好的避免迴圈。for
,while
語句是一種拖累;即使是快速列舉也一樣。無論你怎麼樣使他們看起來更加的友好,迴圈依然是一個在自然語言中用非常簡單方式描述所做事情的程式碼塊。
“給我這個列表裡面所有員工的平均薪酬”,等等。。。
double totalSalary = 0.0; for(Employee *employee in employees){ totalSalary += employee.salary; } double averageSalary = totalSalary / employees.count;
慶幸的是,IOS中的鍵值編碼給了我們更簡潔的語法,來處理這件事情:
[employee valueForKeyPath:@"@avg.salary"];
KVC 集合運算子允許在valueForKeyPath:
方法中使用 key path 符號在一個集合中執行方法。無論什麼時候你在 key path 中看見了@
,它都代表了一個特定的集合方法,其結果可以被返回或者連結,就像其他的 key path 一樣。
集合運算子會根據其返回值的不同分為以下三種類型:
- 簡單的集合運算子 返回的是 strings, number, 或者 dates
- 物件運算子 返回的是一個數組
- 陣列和集合運算子 返回的是一個數組或者集合
要理解其工作原理,最好方式就是去 action 裡面看看。想象一個Product
類和一個由以下資料所組成的products
陣列:
@interface Product : NSObject
@property (nonatomic,copy)NSString *name; //名稱
@property (nonatomic,assign)double price; //價格
@property (nonatomic,strong)NSDate *launchedOn; //上市時間
@end
鍵-值 編碼會在必要的時候把基本資料型別的資料自動裝箱和拆箱到NSNumber
NSValue
中來確保一切工作正常。
簡單集合操作符
@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"]; // June 11, 2012
//提示:你可以簡單的通過把 self 作為操作符後面的 key path 來獲取一個由NSNumber組成的陣列或者集合的總值,例如[@[@(1), @(2), @(3)] valueForKeyPath:@"@max.self"]
物件操作符
想象下,我們有一個inventory
陣列,代表了當地蘋果商店的當前庫存(iPad Mini 不足,並且沒有新的 iMac,因為還沒有發貨):
NSArray *inventory = @["iPhone 5", "iPhone 5", "iPhone 5", "iPad Mini", "MacBook Pro", "MacBook Pro"];
@unionOfObjects
/ @distinctUnionOfObjects
: 返回一個由操作符右邊的 key path 所指定的物件屬性組成的陣列。其中@distinctUnionOfObjects
會對陣列去重, 而且 @unionOfObjects
不會.
例如:
[inventory valueForKeyPath:@"@unionOfObjects.name"]; // "iPhone 5", "iPhone 5", "iPhone 5", "iPad Mini", "MacBook Pro", "MacBook Pro"
[inventory valueForKeyPath:@"@distinctUnionOfObjects.name"]; // "iPhone 5", "iPad Mini", "MacBook Pro"
陣列和集合操作符
陣列和集合操作符跟物件操作符很相似,只不過它是在NSArray
和NSSet
所組成的集合中工作的。如果我們做一些例如:比較幾個商店中的庫存(和我們上一節類似的appleStore庫存
和買 iPhone 5 和 iPad Mini 的versizonStore庫存
)這樣的工作,這個就會很有用。
-
@distinctUnionOfArrays
/@unionOfArrays
: 返回了一個數組,其中包含這個集合中每個陣列對於這個操作符右面指定的 key path 進行操作之後的值。正如你期望的,distinct
版本會移除重複的值。 -
@distinctUnionOfSets
: 和@distinctUnionOfArrays
差不多, 但是它期望的是一個包含著NSSet
物件的NSSet
,並且會返回一個NSSet
物件。因為集合不能包含重複的值,所以它只有distinct
操作。
例如:
NSArray *appleStoreInventory = @[@"iPhone 5", @"iPhone 5s", @"iPhone 6",@"iPhone X",@"iPhone XS",@"iPhone XR",@"iPhone XS Max"];
NSArray *storeInventory = @[@"iPhone XS",@"iPhone XR",@"iPhone XS Max"];
//"iPhone XS Max","iPhone XS","iPhone 5s","iPhone 6","iPhone X","iPhone 5","iPhone XR"
NSLog(@"陣列操作-去重:%@",[@[appleStoreInventory,storeInventory] valueForKeyPath:@"@distinctUnionOfArrays.self"]);
//"iPhone 5","iPhone 5s","iPhone 6","iPhone X","iPhone XS","iPhone XR","iPhone XS Max","iPhone XS","iPhone XR","iPhone XS Max"
NSLog(@"陣列操作-不去重:%@",[@[appleStoreInventory,storeInventory] valueForKeyPath:@"@unionOfArrays.self"]);
NSSet *appleStoreInventory1 = [NSSet setWithArray:appleStoreInventory];
NSSet *storeInventory1 = [NSSet setWithArray:storeInventory];
//"iPhone XR","iPhone 5","iPhone XS","iPhone XS Max","iPhone 5s","iPhone 6","iPhone X"
NSLog(@"集合操作-去重:%@",[[NSSet setWithArray:@[appleStoreInventory1,storeInventory1]] valueForKeyPath:@"@distinctUnionOfSets.self"]);
支援自定義麼?這可能是一個可怕的想法
然而,事實證明,在我們的小夥伴objc/runtime
的幫助下,這個實際上 是 有可以能的實現的。
Guy English有一篇很神奇的文章,在文章中,他swizzles valueForKeyPath:
來解析自定義的DSL,其擴充套件了一些有趣的效果:
NSArray *names = [allEmployees valueForKeyPath: @"[collect].{daysOff<10}.name"];
這是一個好的想法嗎?可能不是。(NSPredicate
更加合適,並且其使得邏輯更加簡單,易懂)
KVC 集合運算子是一個想節省幾行程式碼並在這一過程中看起來很酷的人必須要了解的。當像 Ruby 這樣的指令碼語言自誇它的單行能力是多麼的靈活時,我們也許應該花一點兒時間來慶祝 Objective-C 中的約束和集合操作符。