ios中集合遍歷方法的比較和技巧

本文原文發表自個人【自建博客】,cnblogs同步發表,格式未經調整,內容以原博客爲準

我是前言

集合的遍歷操做是開發中最多見的操做之一,從C語言經典的for循環到利用多核cpu的優點進行遍歷,開發中ios有若干集合遍歷方法,本文經過研究和測試比較了各個操做方法的效率和優略勢,並總結幾個使用集合遍歷時的小技巧。html


ios中經常使用的遍歷運算方法

遍歷的目的是獲取集合中的某個對象或執行某個操做,因此能知足這個條件的方法均可以做爲備選:ios

  • 經典for循環
  • for in (NSFastEnumeration),若不熟悉能夠參考《nshipster介紹NSFastEnumeration的文章》
  • makeObjectsPerformSelector
  • kvc集合運算符
  • enumerateObjectsUsingBlock
  • enumerateObjectsWithOptions(NSEnumerationConcurrent)
  • dispatch_apply

實驗

實驗條件

測試類以下:objective-c

1
2
3
4
@interface Sark : NSObject
@property (nonatomic) NSInteger number;
- (void)doSomethingSlow; // sleep(0.01)
@end

實驗從兩個方面來評價:數組

  1. 分別使用有100個對象和1000000個對象的NSArray,只取對象,不執行操做,測試遍歷速度
  2. 使用有100個對象的NSArray遍歷執行doSomethingSlow方法,測試遍歷中多任務運行速度

實驗使用CFAbsoluteTimeGetCurrent()記錄時間戳來計算運行時間,單位秒。
運行在iphone5真機(雙核cpu)併發

實驗數據

100對象遍歷操做:app

1
2
3
4
5
6
7
經典for循環 - 0.001355
for in (NSFastEnumeration) - 0.002308
makeObjectsPerformSelector - 0.001120
kvc集合運算符(@sum.number) - 0.004272 
enumerateObjectsUsingBlock - 0.001145
enumerateObjectsWithOptions(NSEnumerationConcurrent) - 0.001605
dispatch_apply(Concurrent) - 0.001380

1000000對象遍歷操做:iphone

1
2
3
4
5
6
7
經典for循環 - 1.246721
for in (NSFastEnumeration) - 0.025955
makeObjectsPerformSelector - 0.068234
kvc集合運算符(@sum.number) - 21.677246
enumerateObjectsUsingBlock - 0.586034
enumerateObjectsWithOptions(NSEnumerationConcurrent) - 0.722548
dispatch_apply(Concurrent) - 0.607100

100對象遍歷執行一個很費時的操做:測試

1
2
3
4
5
6
7
經典for循環 - 1.106567
for in (NSFastEnumeration) - 1.102643
makeObjectsPerformSelector - 1.103965
kvc集合運算符(@sum.number) - N/A
enumerateObjectsUsingBlock - 1.104888
enumerateObjectsWithOptions(NSEnumerationConcurrent) - 0.554670
dispatch_apply(Concurrent) - 0.554858

值得注意的

  • 對於集合中對象數不少的狀況下,for in (NSFastEnumeration)的遍歷速度很是之快,但小規模的遍歷並不明顯(還沒普通for循環快)
  • 使用kvc集合運算符運算很大規模的集合時,效率明顯降低(100萬的數組離譜的21秒多),同時佔用了大量內存和cpu
  • enumerateObjectsWithOptions(NSEnumerationConcurrent)dispatch_apply(Concurrent)的遍歷執行能夠利用到多核cpu的優點(實驗中在雙核cpu上效率基本上x2)

遍歷實踐Tips

倒序遍歷

NSArrayNSOrderedSet都支持使用reverseObjectEnumerator倒序遍歷,如:ui

1
2
3
4
NSArray *strings = @[@"1", @"2", @"3"];
for (NSString *string in [strings reverseObjectEnumerator]) {
    NSLog(@"%@", string);
}

這個方法只在循環第一次被調用,因此也沒必要擔憂循環每次計算的問題。atom

同時,使用enumerateObjectsWithOptions:NSEnumerationReverse也能夠實現倒序遍歷:

1
2
3
[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(Sark *sark, NSUInteger idx, BOOL *stop) {
    [sark doSomething];
}];

使用block同時遍歷字典key,value

block版本的字典遍歷能夠同時取key和value(forin只能取key再手動取value),如:

1
2
3
4
NSDictionary *dict = @{@"a": @"1", @"b": @"2"};
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
    NSLog(@"key: %@, value: %@", key, obj);
}];

對於耗時且順序無關的遍歷,使用併發版本

1
2
3
[array enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(Sark *sark, NSUInteger idx, BOOL *stop) {
    [sark doSomethingSlow];
}];

遍歷執行block會分配在多核cpu上執行(底層極可能就是gcd的併發queue),對於耗時的任務來講是很值得這麼作的,並且在之後cpu升級成更多核心後不用改代碼也能夠享受帶來的好處。同時,對於遍歷的外部是保持同步的(遍歷都完成後才繼續執行下一行),猜測內部大概是gcd的dispatch_group或者信號量控制。

代碼可讀性和效率的權衡

雖說上面的測試結果代表,在集合內元素很少時,經典for循環的效率要比forin要高,可是從代碼可讀性上來看,就遠不如forin看着更順暢;一樣的還有kvc的集合運算符,一些內置的操做以keypath的方式聲明,相比本身用for循環實現,一行代碼就能搞定,清楚明瞭,還省去了重複工做;在framework中增長了集合遍歷的block支持後,對於須要index的遍歷不再須要經典for循環的寫法了。


References

http://nshipster.com/enumerators/
http://iosdevelopertips.com/objective-c/fast-enumeration-on-the-iphone.html


原創文章,轉載請註明源地址,blog.sunnyxx.com

相關文章
相關標籤/搜索