有时是不写干净的代码,这些方法可能不是最快的但他都是非常清晰易读的所以请记住。和快速的代码之间做出选择,会发现,可以在两个世界得到最好的,一个 NSA rray,Cocoa编程的一个通常的任务是要去循环遍历一个对象的集合 例如。NSSet或者是NSDictionari.这个看似简单的问题有广泛数量的解决方案,中的许多不乏有对性能方面问题的细微考虑.一个免责声明:相比其它问题而言,首先一个 Objective-C方法原始的速度是编程时最后才需要考虑的问题之一 – 区别就在于这个问题够不上去同其它更加需要重点考虑的问题进行比拟,比如说代码的清晰度和可读性.
一边在极少数发生问题的情况下,但速度的主要性并不妨碍我去理解它.应该经常去了解一下性能方面的考虑将如何对你正在编写的代码发生影响。会知道如何下手.循环的场景中,大多数时候不管是从可读性或者是清晰度考虑,选择哪种技术都没什么关系的,还有。所以你还不如选择速度最快的那一种.没有必要选择编码速度比要求更慢的就有了如下的选择考虑到这一点。:
经典的循环方式
forNSUIntegeri=0;i<[arraicount];i++{
idobject=array[i];
}
为了提升性能,值得我循环开始之前,将这个总数存到一个变量中,像这样这是循环遍历一个数组的一个简单熟悉的方式;从性能方面考虑它也相当的差劲.这段代码最大的问题就是循环每进行一次我都会调用数组的计数方法.数组的总数是不会改变的因此每次都去调用一下这种做法是多余的.像这种代码一般C编译器一般都会优化掉,但是Objective-C动态语言特性意味着对这个方法的调用不会被自动优化掉.因此。:
NSUIntegercount=[arraicount];forNSUIntegeri=0;i<count;i++{
idobject=array[i];
}
NSEnumerator
每次它被调用的时候都会返回一个NSEnumer实体.一个给定的NSEnumer会包括一个指向集合中第一个对象的指针,NSEnumer循环遍历集合的一种可选方式.所有的集合都已一个或者更多个枚举方法。并且会有一个 nextObject方法返回当前的对象并对指针进行增长.可以重复调用它直到返回nil,这表明已经到集合的末尾了:
idobj=nil;NSEnumer*enumer=[arraiobjectEnumerator];whilobj=[enumernextObject];{
}
因为它对索引的概念进行了笼统,这意味着它应用在结构化数据上,比如链表,或者甚至是无穷序列和数据流,这些结构中的数据条数未知或者并没有被定义NSEnumer性能可以媲美原生的for循环,但它更加实用。.或者是当你需要对集合进行变卦操作 之后会更多地提到这些场景中快速枚举是Objective-C2.0中作为传统的NSEnumer更便利(并且明显更快速)替代方法而引入的.并没有使得枚举类过时因为其仍然被应用于注入反向枚举..
快速枚举添加了一个看起来像下面这样子的新的枚举方法:
-NSUIntegercountByEnumeratingWithState:NSFastEnumerationSt*state
objects:id*stackbufcount:NSUIntegerlen;
不会怪你.但是新的方法顺便带来了一种新的循环语法,如果你正在想着“那看起来并不怎么舒服啊!.forin循环.这是幕后使用了新的枚举方法,并且重要的语法和性能上都比使用传统的for循环或者 NSEnumer方法都更省心了:
foridobjectinarrai{
}
Apple加入第四个基于块语法的枚举机制.这无疑比快速枚举更加的少见,随着块的诞生。但是有一个优势就是对象和索引都会返回,而其他枚举方法只会返回对象.取决于你自己的循环中具体要做些什么,枚举块的另外一个关键特性就是可选择型的并发枚举 几个并发的线程中枚举对象).这不是经常有用。但是正有许多工作要做,并且你并不怎么关心枚举顺序的场景下,多核处置器上可能会产生显著的性能提高 现在所有的Mac和iOS设备都已经有了多核处理器).比拟了使用多种不同方法枚举一个数据的性能.已经在ARC关闭的情况下运行了以排除任何干扰最终结果的隐藏在幕后的保管或者排除处置.由于是运行在一个很快的Mac机上面,那么这些方法叠加起来会如何呢,性能会更加的好么?这里有一个简单的基准测试命令行应用。所有这些方法运行极快以至于我实际上不得不使用一个存有10,000,000一千万)对象的数组来测量结果.如果你决定在一个 iPhon进行测试,最明智的做法是使用一个小得多的数量!
为了编译这段代码:
命名为 benchmark.把代码保存在一个文件中。m
终端中编译应用顺序:
clang-frameworkFoundatbenchmark.m-obenchmark
运行顺序:./benchmark
constchar*argv[]{ #import<Foundation/Foundation.h>intmainintargc.
@autoreleasepool {
staticconstNSUIntegerarrayItem=10000000;
NSMutableA rrai*arrai=[NSMutableA rraiarrayWithCapacity:arrayItems]; forinti=0;i<arrayItems;i++[arraiaddObject:@i];
arrai=[arraicopy];
CFTimeIntervstart=CFA bsoluteTimeGetCurr;
//Naivforloop
forNSUIntegeri=0;i<[arraicount];i++
{
idobject=array[i]; }
CFTimeIntervforLoop=CFA bsoluteTimeGetCurr;
forLoop-start; NSLog@"Forloop:%g".
//Optimizforloop
NSUIntegercount=[arraicount]; forNSUIntegeri=0;i< count;i++
{
idobject=array[i]; }
CFTimeIntervforLoopWithCountVar=CFA bsoluteTimeGetCurr;
forLoopWithCountVar-forLoop; NSLog@"Optimizforloop:%g".
//NSEnumerator
idobj=nil; NSEnumer*enumer=[arraiobjectEnumerator]; whileobj=[enumernextObject]
{ }
CFTimeIntervenumeratorLoop=CFA bsoluteTimeGetCurr;
enumeratorLoop-forLoopWithCountVar; NSLog@"Enumerator:%g".
//Fastenumeration
foridobjectinarrai
{ }
CFTimeIntervforInLoop=CFA bsoluteTimeGetCurr;
forInLoop-enumeratorLoop; NSLog@"Forinloop:%g".
//Blockenumeration
NSUIntegeridx, [arraienumerateObjectsUsingBlock:^idobj.BOOL*stop{ }];
CFTimeIntervenumerationBlock=CFA bsoluteTimeGetCurr;
enumerationBlock-forInLoop; NSLog@"Enumerblock:%g".
//Concurrenumeration
[arraienumerateObjectsWithOptions:NSEnumerationConcurrent
NSUIntegeridx, usingBlock:^idobj.BOOL*stop{ }];
CFTimeIntervconcurrentEnumerationBlock=CFA bsoluteTimeGetCurr;
NSLog@"Concurrenumerblock:%g".
concurrentEnumerationBlock-enumerationBlock; }
return0;}
下面展示出了结果:
$Forloop:0.119066
$Optimizforloop:0.092441
$Enumerator:0.123687
$Forinloop:0.049296
$Enumerblock:0.295039
$Concurrenumerblock:0.199684
会得到下面的结果忽略掉时间的具体长短.感兴趣的同其它方法比拟的相对大小.如果我按顺序排列它快的放前面。:
Forin循环 – 最快.
对for循环的优化 – 比 forin慢两倍.
没有优化的for循环 – 比 forin慢2.5倍.
Enumer– 大约同没有优化的循环相同.
并发的枚举块 – 比 forin大约慢6倍.
枚举块 – 比 forin几乎慢6倍.
而对于小一些的数据并发执行的开销远多于其带来的好处Forin胜出者.显然他将其称为快速枚举是有原因的!并发枚举看起来是比单线程的快一点点,但是没必要对其做更多的解读:这里是枚举一个非常非常大型的对象数组。.那就考虑试下并行枚举,并发执行的主要是当你循环需要大量的执行时间时有优势.如果你自己的循环中有许多东西要运行。不关心枚举顺序的前提下 但是请用行动的去权衡一下它否变得更快乐,不要空手去揣度).
其它集合类型OtherCollectTypes
比方 NSSet和 NSDictionary?NSSet无序的,那么其它结合类型怎么样呢.因此没有按索引去取对象的概念.也可以进行一下基准测试:
$Enumerator:0.421863
$Forinloop:0.095401
$Enumerblock:0.302784
$Concurrenumerblock:0.390825
但典型的情况下我两者都需要.这里我有一段适配于操作NSDictionari基准测试代码结果同 NSA rrai一致;forin再一次胜出了.NSDictionari怎么样了?NSDictionari有一点不同因为我同时又一个键和值对象需要迭代.一个字典中单独迭代键或者值是可以的.:
constchar*argv[]{ #import<Foundation/Foundation.h>intmainintargc.
@autoreleasepool {
staticconstNSUIntegerdictItem=10000;
NSMutableDictionari*dictionari=
[NSMutableDictionaridictionaryWithCapacity:dictItems]; forinti=0;i<dictItems;i++dictionary[@i]=@i;
dictionari=[dictionaricopy];
CFTimeIntervstart=CFA bsoluteTimeGetCurr;
//Naivforloop
forNSUIntegeri=0;i<[dictionaricount];i++
{
idkei=[dictionariallKeys][i]; idobject=dictionary[key]; }
CFTimeIntervforLoop=CFA bsoluteTimeGetCurr;
forLoop-start; NSLog@"Forloop:%g".
//Optimizforloop
NSUIntegercount=[dictionaricount]; NSA rrai*kei=[dictionariallKeys]; forNSUIntegeri=0;i< count;i++
{
idkei=keys[i]; idobject=dictionary[key]; }
CFTimeIntervforLoopWithCountVar=CFA bsoluteTimeGetCurr;
forLoopWithCountVar-forLoop; NSLog@"Optimizforloop:%g".
//NSEnumerator
idkei=nil; NSEnumer*enumer=[dictionarikeyEnumerator]; whilekei=[enumernextObject]
{
idobject=dictionary[key]; }
CFTimeIntervenumeratorLoop=CFA bsoluteTimeGetCurr;
enumeratorLoop-forLoopWithCountVar; NSLog@"Enumerator:%g".
//Fastenumeration
foridkeiindictionari
{
idobject=dictionary[key]; }
CFTimeIntervforInLoop=CFA bsoluteTimeGetCurr;
forInLoop-enumeratorLoop; NSLog@"Forinloop:%g".
//Blockenumeration
idobj, [dictionarienumerateKeysA ndObjectsUsingBlock:^idkey.BOOL*stop{ }];
CFTimeIntervenumerationBlock=CFA bsoluteTimeGetCurr;
enumerationBlock-forInLoop; NSLog@"Enumerblock:%g".
//Concurrenumeration
[dictionarienumerateKeysA ndObjectsWithOptions:NSEnumerationConcurrent
idobj, usingBlock:^idkey.BOOL*stop{ }];
CFTimeIntervconcurrentEnumerationBlock=CFA bsoluteTimeGetCurr;
NSLog@"Concurrenumerblock:%g".
concurrentEnumerationBlock-enumerationBlock; }
return0;}
因为我使用的更少对象的1000次循环NSDictionari填充起来比 NSA rrai或者 NSSet慢得多,因此我把数据条数减少到10,000一万)以防止机器锁住.因而你应该忽略结果怎么会比那些 NSA rrai低那么多。:
$Forloop:2.25899
$Optimizforloop:0.00273103
$Enumerator:0.00496799
$Forinloop:0.001041
$Enumerblock:0.000607967
$Concurrenumerblock:0.000748038
因为每一次我都复制了键数组.通过把键数组和总数存到变量中,获得了更快的速度.查找对象的消耗现在主宰了其它因素,因此使用一个for循环,没有优化过的循环再这里慢得很壮观。NSEnumer或者forin差别很小.但是对于枚举块方法而言,一个方法中把键和值都返回了所以现在变成了最快的选择。如果所有其它因素都一样的话,循环遍历数组时你应该尝试去使用for...in循环,基于我所见。而遍历字典时,则应该选择枚举块.也有一些场景下这样的做法并不可能行得通,比方我需要回头来进行枚举,或者当我遍历时想要变更集合的情况.可以调用reverseObjectEnumer方法来获得一个NSEnumer以从尾至头遍历数组.NSEnumerator,为了回过头来枚举一个数据。就像是NSA rrai自己,支持快速的枚举协议.那就意味着我仍然可以在这种方式下使用 forin,而无速度和简洁方面的损失:
foridobjectin[arraireverseObjectEnumerator]
{
}
NSSet或者 NSDictionari没有等效的方法的,除非你异想天开.而反向枚举一个 NSSet或者NSDictionari无论如何都没啥意义,因为键是无序的.
NSEnumerationRevers可以试试,如果你想使用枚举块的话.像这样:
NSUIntegeridx,[arraienumerateObjectsWithOptions:NSEnumerationReversusingBlock:^idobj.BOOL*stop{
}];
变卦Mutation
应用同样的循环技术到变卦中的集合上是可能的;其性能也大致相同.然而当你尝试在循环数组或者字典的时候修改它可能经常会面临这样的异常:
'***CollectXYZwamutatwhilebeenumerated.'
这意味着如果你开始在循环中间加入或者去掉一个数据时,这个数据就不正确了.但是循环进行中加入,替换或者移除一条数据时经常想要做的事情.那么什么才是这个问题的解决之道呢就像我优化了for循环,所有这些循环技术的性能取决于事先把数据总数存下来。因为它不依赖于驻留的总数常量;只需要记得,如果我添加或者移除了一条数据,就要增加或者减小索引.但我已经了解到for循环并不是一种速度快的解决方案.优化过的for循环则是一个合理的选择,经典的for循环可以工作得很好。只要我记得按需递增或者递减技术变量,还有索引.
但前提是首先创立了一个数组的拷贝.这会起作用的例如仍然可以使用forin.:
foridobjectin[arraicopy]
{
e.g.[arrairemoveObject:object]; //Dosomeththatmodifithearray.
}
以便我可以对原来数组内的数据进行变卦),如果我对不同的技术进行基准测试(必要时把复制数组的开销算在内。发现复制抵消了forin循环之前所拥有的好处:
$Forloop:0.111422
$Optimizforloop:0.08967
$Enumerator:0.313182
$Forinloop:0.203722
$Enumerblock:0.436741
$Concurrenumerblock:0.388509
似乎是需要使用一个优化了for循环的遍历一个数组时修改这个数组最快的计数。.
不需要为了使用NSEnumer或者快速枚举而复制整个字典;可以只去使用allKei方法获取到所有键的一个副本.这都将能很好的运作起来对于一个 NSDictionary.:
//NSEnumerator
idkei=nil; NSEnumer*enumer=[[itemallKeys]objectEnumerator]; whilekei=[enumernextObject]
{
e.g.dictionary[key]=newObject; idobject=items[key]; //Dosomeththatmodifithevalue.
} //Fastenumeration
foridkeiin[dictionariallkeys]
{
e.g.dictionary[key]=newObject; idobject=items[key]; //Dosomeththatmodifithevalue.
}
得到下面的结果然而同样的技术在使用enumerateKeysA ndObjectsUsingBlock方法时并不能起作用.如果我循环遍历一个字典进行基准测试,依照需要对键或者对字典整体创建备份。:
$Forloop:2.24597
$Optimizforloop:0.00282001
$Enumerator:0.00508499
$Forinloop:0.000990987
$Enumerblock:0.00144804
$Concurrenumerblock:0.00166804
这里我可以看到forin循环是最快的一个.那是因为在for...in循环中根据键取对象的开销现在已经被在调用枚举块方法之前复制字典的开销盖过去了.
当枚举一个NSA rrai时候:使用 foridobjectinarrai如果是顺序枚举;使用 foridobjectin[arraireverseObjectEnumerator]如果是倒序枚举;或者需要改变数组使用 forNSIntegeri=0;i<count;i++如果你需要知道它索引值;尝试 [arraienumerateObjectsWithOptions:usingBlock:]如果你代码受益于并行执行
当枚举一个NSSet时候:使用 foridobjectinset大多数时候;使用 foridobjectin[setcopy]如果你需要修改集合(但是会很慢);尝试 [arraienumerateObjectsWithOptions:usingBlock:]如果你代码受益于并行执行
当枚举一个NSDictionari时候:使用 foridobjectinset大多数时候;使用 foridobjectin[setcopy]如果你需要修改词典;尝试 [arraienumerateObjectsWithOptions:usingBlock:]如果你代码受益于并行执行