標籤:blog class code tar get string int 2014 html set cti
本文原文發表自我的【自建部落格】,cnblogs同步發表,格式未經調整,內容以原部落格為準我是前言
集合的遍曆操作是開發中最常見的操作之一,從C語言經典的for迴圈到利用多核cpu的優勢進行遍曆,開發中ios有若干集合遍曆方法,本文通過研究和測試比較了各個操作方法的效率和優略勢,並總結幾個使用集合遍曆時的小技巧。
ios中常用的遍曆運算方法
遍曆的目的是擷取集合中的某個對象或執行某個操作,所以能滿足這個條件的方法都可以作為備選:
- 經典for迴圈
- for in (NSFastEnumeration),若不熟悉可以參考《nshipster介紹NSFastEnumeration的文章》
- makeObjectsPerformSelector
- kvc集合運算子
- enumerateObjectsUsingBlock
- enumerateObjectsWithOptions(NSEnumerationConcurrent)
- dispatch_apply
實驗實驗條件
測試類別如下:
1234 |
@interface Sark : NSObject@property (nonatomic) NSInteger number;- (void)doSomethingSlow; // sleep(0.01)@end |
實驗從兩個方面來評價:
- 分別使用有100個對象和1000000個對象的NSArray,只取對象,不執行操作,測試遍曆速度
- 使用有100個對象的NSArray遍曆執行
doSomethingSlow
方法,測試遍曆中多任務運行速度
實驗使用CFAbsoluteTimeGetCurrent()
記錄時間戳記來計算已耗用時間,單位秒。
運行在iphone5真機(雙核cpu)
實驗資料
100對象遍曆操作:
1234567 |
經典for迴圈 - 0.001355for in (NSFastEnumeration) - 0.002308makeObjectsPerformSelector - 0.001120kvc集合運算子(@sum.number) - 0.004272 enumerateObjectsUsingBlock - 0.001145enumerateObjectsWithOptions(NSEnumerationConcurrent) - 0.001605dispatch_apply(Concurrent) - 0.001380 |
1000000對象遍曆操作:
1234567 |
經典for迴圈 - 1.246721for in (NSFastEnumeration) - 0.025955makeObjectsPerformSelector - 0.068234kvc集合運算子(@sum.number) - 21.677246enumerateObjectsUsingBlock - 0.586034enumerateObjectsWithOptions(NSEnumerationConcurrent) - 0.722548dispatch_apply(Concurrent) - 0.607100 |
100對象遍曆執行一個很費時的操作:
1234567 |
經典for迴圈 - 1.106567for in (NSFastEnumeration) - 1.102643makeObjectsPerformSelector - 1.103965kvc集合運算子(@sum.number) - N/AenumerateObjectsUsingBlock - 1.104888enumerateObjectsWithOptions(NSEnumerationConcurrent) - 0.554670dispatch_apply(Concurrent) - 0.554858 |
值得注意的
- 對於集合中對象數很多的情況下,
for in (NSFastEnumeration)
的遍曆速度非常之快,但小規模的遍曆並不明顯(還沒普通for迴圈快)
- 使用
kvc集合運算子
運算很大規模的集合時,效率明顯下降(100萬的數組離譜的21秒多),同時佔用了大量記憶體和cpu
enumerateObjectsWithOptions(NSEnumerationConcurrent)
和dispatch_apply(Concurrent)
的遍曆執行可以利用到多核cpu的優勢(實驗中在雙核cpu上效率基本上x2)
遍曆實踐Tips倒序遍曆
NSArray
和NSOrderedSet
都支援使用reverseObjectEnumerator
倒序遍曆,如:
1234 |
NSArray *strings = @[@"1", @"2", @"3"];for (NSString *string in [strings reverseObjectEnumerator]) { NSLog(@"%@", string);} |
這個方法只在迴圈第一次被調用,所以也不必擔心迴圈每次計算的問題。
同時,使用enumerateObjectsWithOptions:NSEnumerationReverse
也可以實現倒序遍曆:
123 |
[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(Sark *sark, NSUInteger idx, BOOL *stop) { [sark doSomething];}]; |
使用block同時遍曆字典key,value
block版本的字典遍曆可以同時取key和value(forin只能取key再手動取value),如:
1234 |
NSDictionary *dict = @{@"a": @"1", @"b": @"2"};[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { NSLog(@"key: %@, value: %@", key, obj);}]; |
對於耗時且順序無關的遍曆,使用並發版本
123 |
[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