集合的遍歷操作是開發(fā)中最常見的操作之一菩佑,從C語言經(jīng)典的for循環(huán)到利用多核cpu的優(yōu)勢(shì)進(jìn)行遍歷,開發(fā)中ios有若干集合遍歷方法,本文通過研究和測(cè)試比較了各個(gè)操作方法的效率和優(yōu)略勢(shì),并總結(jié)幾個(gè)使用集合遍歷時(shí)的小技巧。
ios中常用的遍歷運(yùn)算方法
遍歷的目的是獲取集合中的某個(gè)對(duì)象或執(zhí)行某個(gè)操作产园,所以能滿足這個(gè)條件的方法都可以作為備選:
經(jīng)典for循環(huán)
for in (NSFastEnumeration),若不熟悉可以參考《nshipster介紹NSFastEnumeration的文章》
makeObjectsPerformSelector
kvc集合運(yùn)算符
enumerateObjectsUsingBlock
enumerateObjectsWithOptions(NSEnumerationConcurrent)
dispatch_apply
實(shí)驗(yàn)
實(shí)驗(yàn)條件
測(cè)試類如下:
@interface Sark : NSObject
@property (nonatomic) NSInteger number;
- (void)doSomethingSlow; // sleep(0.01)
@end
實(shí)驗(yàn)從兩個(gè)方面來評(píng)價(jià):
1夜郁、分別使用有100個(gè)對(duì)象和1000000個(gè)對(duì)象的NSArray什燕,只取對(duì)象,不執(zhí)行操作竞端,測(cè)試遍歷速度
2屎即、使用有100個(gè)對(duì)象的NSArray遍歷執(zhí)行doSomethingSlow方法,測(cè)試遍歷中多任務(wù)運(yùn)行速度
實(shí)驗(yàn)使用CFAbsoluteTimeGetCurrent()記錄時(shí)間戳來計(jì)算運(yùn)行時(shí)間事富,單位秒技俐。
運(yùn)行在iphone5真機(jī)(雙核cpu)
實(shí)驗(yàn)數(shù)據(jù)
100對(duì)象遍歷操作:
經(jīng)典for循環(huán) - 0.001355
for in (NSFastEnumeration) - 0.002308
makeObjectsPerformSelector - 0.001120
kvc集合運(yùn)算符(@sum.number) - 0.004272
enumerateObjectsUsingBlock - 0.001145
enumerateObjectsWithOptions(NSEnumerationConcurrent) - 0.001605
dispatch_apply(Concurrent) - 0.001380
1000000對(duì)象遍歷操作:
經(jīng)典for循環(huán) - 1.246721
for in (NSFastEnumeration) - 0.025955
makeObjectsPerformSelector - 0.068234
kvc集合運(yùn)算符(@sum.number) - 21.677246
enumerateObjectsUsingBlock - 0.586034
enumerateObjectsWithOptions(NSEnumerationConcurrent) - 0.722548
dispatch_apply(Concurrent) - 0.607100
100對(duì)象遍歷執(zhí)行一個(gè)很費(fèi)時(shí)的操作:
經(jīng)典for循環(huán) - 1.106567
for in (NSFastEnumeration) - 1.102643
makeObjectsPerformSelector - 1.103965
kvc集合運(yùn)算符(@sum.number) - N/A
enumerateObjectsUsingBlock - 1.104888
enumerateObjectsWithOptions(NSEnumerationConcurrent) - 0.554670
dispatch_apply(Concurrent) - 0.554858
值得注意的
- 對(duì)于集合中對(duì)象數(shù)很多的情況下,for in (NSFastEnumeration)的遍歷速度非常之快统台,但小規(guī)模的遍歷并不明顯(還沒普通for循環(huán)快)
- 使用kvc集合運(yùn)算符運(yùn)算很大規(guī)模的集合時(shí)雕擂,效率明顯下降(100萬的數(shù)組離譜的21秒多),同時(shí)占用了大量?jī)?nèi)存和cpu
- enumerateObjectsWithOptions(NSEnumerationConcurrent)和dispatch_apply(Concurrent)的遍歷執(zhí)行可以利用到多核cpu的優(yōu)勢(shì)(實(shí)驗(yàn)中在雙核cpu上效率基本上x2)
遍歷實(shí)踐Tips
倒序遍歷
NSArray和NSOrderedSet都支持使用reverseObjectEnumerator倒序遍歷贱勃,如:
NSArray *strings = @[@"1", @"2", @"3"];
for (NSString *string in [strings reverseObjectEnumerator]) {
NSLog(@"%@", string);
}
這個(gè)方法只在循環(huán)第一次被調(diào)用井赌,所以也不必?fù)?dān)心循環(huán)每次計(jì)算的問題谤逼。
同時(shí),使用enumerateObjectsWithOptions:NSEnumerationReverse也可以實(shí)現(xiàn)倒序遍歷:
[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(Sark *sark, NSUInteger idx, BOOL *stop) {
[sark doSomething];
}];
使用block同時(shí)遍歷字典key仇穗,value
block版本的字典遍歷可以同時(shí)取key和value(forin只能取key再手動(dòng)取value)流部,如:
NSDictionary *dict = @{@"a": @"1", @"b": @"2"};
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSLog(@"key: %@, value: %@", key, obj);
}];
對(duì)于耗時(shí)且順序無關(guān)的遍歷,使用并發(fā)版本
[array enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(Sark *sark, NSUInteger idx, BOOL *stop) {
[sark doSomethingSlow];
}];
遍歷執(zhí)行block會(huì)分配在多核cpu上執(zhí)行(底層很可能就是gcd的并發(fā)queue)纹坐,對(duì)于耗時(shí)的任務(wù)來說是很值得這么做的枝冀,而且在以后cpu升級(jí)成更多核心后不用改代碼也可以享受帶來的好處。同時(shí)耘子,對(duì)于遍歷的外部是保持同步的(遍歷都完成后才繼續(xù)執(zhí)行下一行)果漾,猜想內(nèi)部大概是gcd的dispatch_group或者信號(hào)量控制。
代碼可讀性和效率的權(quán)衡
雖然說上面的測(cè)試結(jié)果表明谷誓,在集合內(nèi)元素不多時(shí)绒障,經(jīng)典for循環(huán)的效率要比forin要高,但是從代碼可讀性上來看片林,就遠(yuǎn)不如forin看著更順暢端盆;同樣的還有kvc的集合運(yùn)算符怀骤,一些內(nèi)置的操作以keypath的方式聲明费封,相比自己用for循環(huán)實(shí)現(xiàn),一行代碼就能搞定蒋伦,清楚明了弓摘,還省去了重復(fù)工作;在framework中增加了集合遍歷的block支持后痕届,對(duì)于需要index的遍歷再也不需要經(jīng)典for循環(huán)的寫法了韧献。
References
http://nshipster.com/enumerators/
http://iosdevelopertips.com/objective-c/fast-enumeration-on-the-iphone.html