Objective-C 数组遍历的性能及原理

fp_hvey 2年前
   <p>数组的遍历,这个话题貌似没什么好探究的,该怎么遍历就怎么遍历呗!但是如果要回答这些问题:</p>    <p><em>OC数组有哪几种遍历方式?</em></p>    <p><em>哪种方式效率最高?为什么?</em></p>    <p><em>各种遍历方式的内部实现是怎么样的?</em></p>    <p><em>NS(Mutable)Array的内部结构是怎么样的?</em></p>    <p>我觉得还是需要探究一下.</p>    <h2>一.OC数组的类体系</h2>    <p>当我们创建一个NSArray对象时,实际上得到的是NSArray的子类 __NSArrayI 对象.同样的,我们创建 NSMutableArray 对象,得到的同样是其子类 __NSArrayM 对象.有趣的是,当我们创建只有一个对象的 NSArray 时,得到的是 __NSSingleObjectArrayI 类对象.</p>    <p>__NSArrayI 和 __NSArrayM , __NSSingleObjectArrayI 为框架隐藏的类.</p>    <p>OC数组的类体系如下:</p>    <p><img src="https://simg.open-open.com/show/a7c6e0ae38d672920ddeb3d6519041c8.png"></p>    <p>通过NSArray和NSMutableArray接口,返回的却是子类对象,怎么做到的?</p>    <p>先介绍另一个私有类: __NSPlaceholderArray ,和两个此类的全局变量 ___immutablePlaceholderArray , ___mutablePlaceholderArray 。 __NSPlaceholderArray 从类命名上看,它只是用来占位的,具体怎么占位法稍后讨论,有个重要特点是, __NSPlaceholderArray 实现了和 NSArray , NSMutableArray 一摸一样的初始化方法,如 initWithObjects:count: , initWithCapacity: 等.</p>    <p>介绍完 __NSPlaceholderArray 后,这个机制可以总结为以下两个大步骤:</p>    <p>(1).NSArray重写了 + (id)allocWithZone:(struct _NSZone *)zone 方法,在方法内部,如果调用类为 NSArray 则直接返回全局变量 ___immutablePlaceholderArray ,如果调用类为 NSMUtableArray 则直接返回全局变量 ___mutablePlaceholderArray 。</p>    <p>也就是调用 [NSArray alloc] 或者 [NSMUtableArray alloc] 得到的仅仅是两个占位指针,类型为 __NSPlaceholderArray .</p>    <p>(2).在调用了 alloc 的基础上,不论是 NSArray 或 NSMutableArray 都必定要继续调用某个 initXXX 方法,而实际上调用的是 __NSPlaceholderArray 的 initXXX .在这个 initXXX 方法内部,如果 self == ___immutablePlaceholderArray 就会重新构造并返回 __NSArrayI 对象,如果 self == ___mutablePlaceholderArray 就会重新构造并返回 _NSArrayM 对象.</p>    <p>总结来说,对于 NSArray 和 NSMutableArray , alloc 时拿到的仅仅是个占位对象, init 后才得到真实的子类对象.</p>    <p>接下来清点一下几种遍历方式:</p>    <h2>二.OC数组遍历的几种方式</h2>    <p>1.for 循环</p>    <pre>  <code class="language-objectivec">for (NSUInteger i = 0;  i < array.count; ++i) {          object = array[i];    }</code></pre>    <p>array[i] 会被编译器转化为对 - (ObjectType)objectAtIndexedSubscript:(NSUInteger)index 的调用,此方法内部调用的就是 - (ObjectType)objectAtIndex:(NSUInteger)index 方法.</p>    <p>2.for in</p>    <pre>  <code class="language-objectivec">for (id obj in array) {          xxx    }</code></pre>    <p>文章稍后会讨论到for in的内部实现</p>    <p>3.enumerateObjectsUsingBlock</p>    <p>通过block回调顺序遍历:</p>    <pre>  <code class="language-objectivec">[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {         xxx    }];</code></pre>    <p>4.enumerateObjectsWithOptions:usingBlock:</p>    <p>通过block回调,在子线程中遍历,对象的回调次序是乱序的,而且调用线程会等待该遍历过程完成:</p>    <pre>  <code class="language-objectivec">[array enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {          xxx    }];</code></pre>    <p>通过block回调,在主线程中逆序遍历:</p>    <pre>  <code class="language-objectivec">[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {          xxx    }];</code></pre>    <p>5.objectEnumerator/reverseObjectEnumerator</p>    <p>通过Enumerator顺序遍历:</p>    <pre>  <code class="language-objectivec">NSEnumerator *enumerator = array.objectEnumerator;  while((object = enumerator.nextObject)) {      xxx  }</code></pre>    <p>通过ReverseEnumerator逆序遍历:</p>    <pre>  <code class="language-objectivec">NSEnumerator *enumerator = array.reverseObjectEnumerator;  while((object = enumerator.nextObject)) {      xxx  }</code></pre>    <p>6.enumerateObjectsAtIndexes:options:usingBlock:</p>    <p>通过block回调,在子线程中对指定IndexSet遍历,对象的回调次序是乱序的,而且调用线程会等待该遍历过程完成:</p>    <pre>  <code class="language-objectivec">[array enumerateObjectsAtIndexes:[NSIndexSet xxx] options:NSEnumerationConcurrent usingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {          xxx   }];</code></pre>    <p>通过block回调,在主线程中对指定IndexSet逆序遍历:</p>    <pre>  <code class="language-objectivec">[array enumerateObjectsAtIndexes:[NSIndexSet xxx] options:NSEnumerationReverse usingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {          xxx   }];</code></pre>    <h2>三.性能比较</h2>    <p>以100为步长,构造对象数目在0-100万之间的 NSArray , 分别用上述的遍历方式进行遍历并计时(单位us),而且在每一次遍历中,仅仅只是得到对象,没有其他任何输入输出,计算之类的干扰操作。每种遍历方式采集得1万组数据,得到如下的性能对比结果:</p>    <p><img src="https://simg.open-open.com/show/6c13bfedd93e60d831ade36e5c736aca.png"></p>    <p>横轴为遍历的对象数目,纵轴为耗时,单位us.</p>    <p>从图中看出,在对象数目很小的时候,各种方式的性能差别微乎其微。随着对象数目的增大, 性能差异才体现出来.</p>    <p>其中 for in 的耗时一直都是最低的,当对象数高达100万的时候, for in 耗时也没有超过5ms.</p>    <p>其次是for循环耗时较低.</p>    <p>反而,直觉上应该非常快速的多线程遍历方式:</p>    <pre>  <code class="language-objectivec">[array enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {          xxx    }];</code></pre>    <p>却是性能最差的。</p>    <p>enumerateObjectsUsingBlock : 和 reverseObjectEnumerator 的遍历性能非常相近.</p>    <p>为什么会有这样的结果,文章稍后会从各种遍历的内部实现来分析原因。</p>    <h2>四.OC数组的内部结构</h2>    <p>NSArray 和 NSMutableArray 都没有定义实例变量,只是定义和实现了接口,且对内部数据操作的接口都是在各个子类中实现的.所以真正需要了解的是子类结构,了解了 __NSArrayI 就相当于了解 NSArray ,了解了 __NSArrayM 就相当于了解 NSMutableArray .</p>    <p>1. __NSArrayI</p>    <p>__NSArrayI的结构定义为:</p>    <pre>  <code class="language-objectivec">@interface __NSArrayI : NSArray  {      NSUInteger _used;      id _list[0];  }  @end</code></pre>    <p>_used 是数组的元素个数,调用 [array count] 时,返回的就是 _used 的值。</p>    <p>id _list[0] 是数组内部实际存储对象的数组,但为何定义为0长度呢?这里有一篇关于0长度数组的文章: <a href="/misc/goto?guid=4959740273001297911" rel="nofollow,noindex">http://blog.csdn.net/zhaqiwen/article/details/7904515</a></p>    <p>这里我们可以把 id _list[0] 当作 id *_list 来用,即一个存储 id 对象的 buff .</p>    <p>由于 __NSArrayI 的不可变,所以 _list 一旦分配,释放之前都不会再有移动删除操作了,只有获取对象一种操作.因此 __NSArrayI 的实现并不复杂.</p>    <p>2. __NSSingleObjectArrayI</p>    <p>__NSSingleObjectArrayI的结构定义为:</p>    <pre>  <code class="language-objectivec">@interface __NSSingleObjectArrayI : NSArray  {      id object;  }  @end</code></pre>    <p>因为只有在"创建只包含一个对象的不可变数组"时,才会得到 __NSSingleObjectArrayI 对象,所以其内部结构更加简单,一个 object 足矣.</p>    <p>3. __NSArrayM</p>    <p>__NSArrayM的结构定义为:</p>    <pre>  <code class="language-objectivec">@interface __NSArrayM : NSMutableArray  {      NSUInteger _used;      NSUInteger _offset;      int _size:28;      int _unused:4;      uint32_t _mutations;      id *_list;  }  @end</code></pre>    <p>__NSArrayM 稍微复杂一些,但是同样的,它的内部对象数组也是一块连续内存 id* _list ,正如 __NSArrayI 的 id _list[0] 一样</p>    <p>_used :当前对象数目</p>    <p>_offset :实际对象数组的起始偏移,这个字段的用处稍后会讨论</p>    <p>_size :已分配的 _list 大小(能存储的对象个数,不是字节数)</p>    <p>_mutations :修改标记,每次对 __NSArrayM 的修改操作都会使 _mutations 加1,“ *** Collection <__NSArrayM: 0x1002076b0> was mutated while being enumerated. ”这个异常就是通过对 _mutations 的识别来引发的</p>    <p>id *_list 是个循环数组.并且在增删操作时会动态地重新分配以符合当前的存储需求.以一个初始包含5个对象,总大小 _size 为6的 _list 为例:</p>    <p>_offset = 0 , _used = 5 , _size=6</p>    <p><img src="https://simg.open-open.com/show/15082a40425826fb02723ea877afc439.png"></p>    <p>在末端追加3个对象后:</p>    <p>_offset = 0 , _used = 8 , _size=8</p>    <p>_list 已重新分配</p>    <p><img src="https://simg.open-open.com/show/8d3afabfbfcb7071128e606cbe16a702.png"></p>    <p>删除对象A:</p>    <p>_offset = 1 , _used = 7 , _size=8</p>    <p><img src="https://simg.open-open.com/show/747b3046a6f3b1b0f327464a1fe7a877.png"></p>    <p>删除对象E:</p>    <p>_offset = 2 , _used = 6 , _size=8</p>    <p>B,C往后移动了,E的空缺被填补</p>    <p><img src="https://simg.open-open.com/show/91a4136c54abfb9d8f8ff242fa3d97ba.png"></p>    <p>在末端追加两个对象:</p>    <p>_offset = 2 , _used = 8 , _size=8</p>    <p>_list 足够存储新加入的两个对象,因此没有重新分配,而是将两个新对象存储到了 _list 起始端</p>    <p><img src="https://simg.open-open.com/show/ed2c7ea998a29c078a1149034621131c.png"></p>    <p>因此可见, __NSArrayM 的 _list 是个循环数组,它的起始由 _offset 标识.</p>    <h2>五.各种遍历的内部实现</h2>    <p>1.快速枚举</p>    <p>前面并没有说过快速枚举这个词,怎么这里突然蹦出来了,实际上for in就是基于快速枚举实现的,但是先不讨论for in,先认识一个协议: NSFastEnumeration ,它的定义在 Foundation 框架的 NSFastEnumeration .h 头文件中:</p>    <pre>  <code class="language-objectivec">@protocol NSFastEnumeration    - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len;    @end</code></pre>    <p>NSFastEnumerationState 定义:</p>    <pre>  <code class="language-objectivec">typedef struct {      unsigned long state;      id __unsafe_unretained _Nullable * _Nullable itemsPtr;      unsigned long * _Nullable mutationsPtr;      unsigned long extra[5];  } NSFastEnumerationState;</code></pre>    <p>看了这些定义和苹果文档,我也不知道究竟怎么用这个方法,它怎么就叫快速枚举了呢,除非知道它的实现细节,否则用的时候疑惑太多了.因此我们就先不管怎么用,而是来看看它的实现细节.</p>    <p>__NSArrayI , __NSArrayM , __NSSingleObjectArrayI 都实现了 NSFastEnumeration 协议.</p>    <p>(1) __NSArrayI的实现:</p>    <p>根据汇编反写可以得到:</p>    <pre>  <code class="language-objectivec">- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len {      if (!buffer && len > 0) {          CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("*** %s: pointer to objects array is NULL but length is %lu"), "-[__NSArrayI countByEnumeratingWithState:objects:count:]",(unsigned long)len);          CFAutorelease(errorString);          [[NSException exceptionWithName:NSInvalidArgumentException reason:(__bridge NSString *)errorString userInfo:nil] raise];      }        if (len >= 0x40000000) {          CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("*** %s: count (%lu) of objects array is ridiculous"), "-[__NSArrayI countByEnumeratingWithState:objects:count:]",(unsigned long)len);          CFAutorelease(errorString);          [[NSException exceptionWithName:NSInvalidArgumentException reason:(__bridge NSString *)errorString userInfo:nil] raise];      }        static const unsigned long mu = 0x01000000;        if (state->state == 0) {          state->mutationsPtr = μ          state->state = ~0;          state->itemsPtr = _list;          return _used;      }      return 0;  }</code></pre>    <p>可见在 __NSArrayI 对这个方法的实现中,主要做的事就是把 __NSArrayI 的内部数组 _list 赋给 state->itemsPtr ,并返回 _used 即数组大小. state->mutationsPtr 指向一个局部静态变量, state->state 看起来是一个标志,如果再次用同一个 state 调用这个方法就直接返回0了.</p>    <p>至于传入的 buffer , len 仅仅只是用来判断了一下参数合理性。</p>    <p>看来有点明白快速枚举的意思了,这一下就把全部对象获取到了,而且在一个c数组里,之后要获得哪个位置的对象都可以快速寻址到,调用方通过 state->itemsPtr 来访问这个数组,通过返回值来确定数组里对象数目.</p>    <p>例如遍历一个 NSArray 可以这样:</p>    <pre>  <code class="language-objectivec">NSFastEnumerationState state = {0};      NSArray *array = @[@1,@2,@3];      id buffer[2];  //buffer 实际上内部没有用上,但还是得传, 2表示我期望得到2个对象,实际上返回的是全部对象数3      NSUInteger n = [array countByEnumeratingWithState:&state objects:buffer count:2];      for (NSUInteger i=0; i<n; ++i) {          NSLog(@"%@", (__bridge NSNumber *)state.itemsPtr[i]);      }</code></pre>    <p>看来之所以叫快速遍历,是因为这种方式直接从c数组里取对象,不用调用别的方法,所以快速.</p>    <p>__NSSingleObjectArrayI 的实现也猜得出了,在此就不贴代码了.我们来看看 __NSArrayM 是怎么实现这个协议的.</p>    <p>(2) __NSArrayM的实现:</p>    <pre>  <code class="language-objectivec">- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len {      if (!buffer && len > 0) {          CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("*** %s: pointer to objects array is NULL but length is %lu"), "-[__NSArrayI countByEnumeratingWithState:objects:count:]",(unsigned long)len);          CFAutorelease(errorString);          [[NSException exceptionWithName:NSInvalidArgumentException reason:(__bridge NSString *)errorString userInfo:nil] raise];      }        if (len >= 0x40000000) {          CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("*** %s: count (%lu) of objects array is ridiculous"), "-[__NSArrayI countByEnumeratingWithState:objects:count:]",(unsigned long)len);          CFAutorelease(errorString);          [[NSException exceptionWithName:NSInvalidArgumentException reason:(__bridge NSString *)errorString userInfo:nil] raise];      }        if (state->state != ~0) {          if (state->state == 0) {              state->mutationsPtr = &_mutations;              //找到_list中元素起始的位置              state->itemsPtr = _list + _offset;              if (_offset + _used <= _size) {                  //必定没有剩余元素                  //标示遍历完成                  state->state = ~0;                  return _used;              }              else {                  //有剩余元素(_list是个循环数组,剩余元素在_list从起始位置开始存储)                  //state->state存放剩余元素数目                  state->state = _offset + _used - _size;                  //返回本次得到的元素数目 (总数 - 剩余)                  return _used - state->state;              }          }          else {              //得到剩余元素指针              state->itemsPtr = _list;              unsigned long left = state->state;              //标示遍历完成了              state->state = ~0;              return left;          }      }      return 0;  }</code></pre>    <p>从实现看出,对于 __NSArrayM ,用快速枚举的方式最多只要两次就可以获取全部元素. 如果 _list 还没有构成循环,那么第一次就获得了全部元素,跟 __NSArrayI 一样。但是如果 _list 构成了循环,那么就需要两次,第一次获取 _offset 到 _list 末端的元素,第二次获取存放在 _list 起始处的剩余元素.</p>    <p>2.for in的实现</p>    <p>如前面性能比较一节提到的,for in的性能是最好的,可以猜测for in基于应该就是刚刚讨论的快速枚举。</p>    <p>如下代码:</p>    <pre>  <code class="language-objectivec">NSArray *arr = @[@1,@2,@3];      for (id obj in arr) {          NSLog(@"obj = %@",obj);      }</code></pre>    <p>通过 clang -rewrite-objc main.m 命令看看编译器把for in变成了什么:</p>    <pre>  <code class="language-objectivec">//NSArray *arr = @[@1,@2,@3];  NSArray *arr = ((NSArray *(*)(Class, SEL, const ObjectType *, NSUInteger))(void *)objc_msgSend)(objc_getClass("NSArray"), sel_registerName("arrayWithObjects:count:"), (const id *)__NSContainer_literal(3U, ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 2), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 3)).arr, 3U);      {  //for (id obj in arr) obj的定义      id obj;  //NSFastEnumerationState      struct __objcFastEnumerationState enumState = { 0 };  //buffer      id __rw_items[16];      id l_collection = (id) arr;  //第一次遍历,调用countByEnumeratingWithState:objects:count:快速枚举方法      _WIN_NSUInteger limit =          ((_WIN_NSUInteger (*) (id, SEL, struct __objcFastEnumerationState *, id *, _WIN_NSUInteger))(void *)objc_msgSend)          ((id)l_collection,          sel_registerName("countByEnumeratingWithState:objects:count:"),          &enumState, (id *)__rw_items, (_WIN_NSUInteger)16);      if (limit) {  //保存初次得到的enumState.mutationsPtr的值      unsigned long startMutations = *enumState.mutationsPtr;      do {          unsigned long counter = 0;          do {  //在获取enumState.itemsPtr中每个元素前,都检查一遍enumState.mutationsPtr所指标志是否改变,改变则抛出异常  //对__NSArrayI,enumState.mutationsPtr指向一个静态局部变量,永远也不会抛异常  //对__NSArrayM,enumState.mutationsPtr指向_mutations变量, 每次增删操作后,_mutations会+1              if (startMutations != *enumState.mutationsPtr)                  objc_enumerationMutation(l_collection);  //获取每一个obj              obj = (id)enumState.itemsPtr[counter++]; {  //NSLog(@"obj = %@",obj);          NSLog((NSString *)&__NSConstantStringImpl__var_folders_rg_wm9xjmyn1kz01_pph_34xcqc0000gn_T_main_c95c5d_mi_8,obj);      };      __continue_label_2: ;          } while (counter < limit);  //再一次遍历,获取剩余元素      } while ((limit = ((_WIN_NSUInteger (*) (id, SEL, struct __objcFastEnumerationState *, id *, _WIN_NSUInteger))(void *)objc_msgSend)          ((id)l_collection,          sel_registerName("countByEnumeratingWithState:objects:count:"),          &enumState, (id *)__rw_items, (_WIN_NSUInteger)16)));  //遍历完成      obj = ((id)0);      __break_label_2: ;      }  //没有元素,空数组      else          obj = ((id)0);      }</code></pre>    <p>可见,for in就是基于快速枚举实现的,编译器将for in转化为两层循环,外层调用快速枚举方法批量获取元素,内层通过c数组取得一批元素中的每一个,并且在每次获取元素前,检查是否对数组对象进行了变更操作,如果是,则抛出异常.</p>    <p>3.enumerateObjectsUsingBlock:</p>    <p>该方法在 NSArray 中实现,所有子类对象调用的都是这个实现</p>    <pre>  <code class="language-objectivec">- (void)enumerateObjectsUsingBlock:(void ( ^)(id obj, NSUInteger idx, BOOL *stop))block {      if (!block) {          CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("*** %s: block cannot be nil"), "-[NSArray enumerateObjectsUsingBlock:]");          CFAutorelease(errorString);          [[NSException exceptionWithName:NSInvalidArgumentException reason:(__bridge NSString *)errorString userInfo:nil] raise];      }        [self enumerateObjectsWithOptions:0 usingBlock:block];  }</code></pre>    <p>内部直接以 option = 0 调用了 enumerateObjectsWithOptions: usingBlock:</p>    <p>4. enumerateObjectsWithOptions: usingBlock:</p>    <p>(1) __NSArrayI 的实现</p>    <pre>  <code class="language-objectivec">- (void)enumerateObjectsWithOptions:(NSEnumerationOptions)opts usingBlock:(void (^)(id _Nonnull, NSUInteger, BOOL * _Nonnull))block {      if (!block) {          CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("*** %s: block cannot be nil"), "-[__NSArrayI enumerateObjectsWithOptions:usingBlock:]");          CFAutorelease(errorString);          [[NSException exceptionWithName:NSInvalidArgumentException reason:(__bridge NSString *)errorString userInfo:nil] raise];      }        __block BOOL stoped = NO;      void (^enumBlock)(NSUInteger idx) = ^(NSUInteger idx) {          if(!stoped) {              @autoreleasepool {                  block(_list[idx],idx,&stoped);              }          }      };        if (opts == NSEnumerationConcurrent) {          dispatch_apply(_used, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), enumBlock);      }      else if(opts == NSEnumerationReverse) {          for (NSUInteger idx = _used - 1; idx != (NSUInteger)-1 && !stoped; idx--) {              enumBlock(idx);          }      }      //opts == 0      else {          if(_used > 0) {              for (NSUInteger idx = 0; idx != _used - 1 && !stoped; idx++) {                  enumBlock(idx);              }          }      }  }</code></pre>    <p>(1) __NSArrayM 的实现</p>    <p>__NSArrayM 的实现唯一不同的是 enumBlock</p>    <pre>  <code class="language-objectivec">void (^enumBlock)(NSUInteger idx) = ^(NSUInteger idx) {          if(!stoped) {              @autoreleasepool {                  NSUInteger idx_ok = _offset + idx;                  //idx对应元素在_list起始处(循环部分)                  if (idx_ok >= _size) {                      idx_ok -= _size;                  }                  block(_list[idx_ok],idx,&stoped);              }          }      };</code></pre>    <p>5.objectEnumerator/reverseObjectEnumerator</p>    <p>通过 array.objectEnumerator 得到的是一个 __NSFastEnumerationEnumerator 私有类对象,在这个 enumerator 对象上每次调用 - (id)nextObject 时,实际上内部每次都调用的是 array 的快速枚举方法:</p>    <pre>  <code class="language-objectivec">- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len</code></pre>    <p>只不过每次只获取并返回一个元素.</p>    <p>而通过 array.reverseObjectEnumerator 得到的是一个 __NSArrayReverseEnumerator 私有类对象,在这个 enumerator 对象上每次调用 - (id)nextObject 时,内部直接调用是: objectAtIndex: 来返回对象.</p>    <p>6.enumerateObjectsAtIndexes:options:usingBlock:</p>    <p>由于时间关系后面再贴了.</p>    <h2>6.总结</h2>    <p>到此,应该可以回答文章开头提到的几个问题了.</p>    <p>关于性能的差异:</p>    <p>for in 之所以快,是因为它基于快速枚举,对 NSArray 只要一次快速枚举调用就可以获取到包含全部元素的c数组,对 NSMUtableArray 最多两次就可以全部获取。</p>    <p>for 之所以比 for in 稍慢,仅仅是因为它函数调用开销的问题,相对于 for in 直接从c数组取每个元素的方式, for 靠的是每次调用 objectAtIndex: 。</p>    <p>而 NSEnumerationConcurrent+Block 的方式耗时最大,我认为是因为它采用了多线程,就这个方法来讲,多线程的优势并不在于遍历有多快,而是在于它的回调在各个子线程,如果有 遍历+分别耗时计算 的场景,这个方法应该是最适合的,只是此处只测遍历速度,它光启动分发管理线程就耗时不少,所以性能落后了.</p>    <p>希望通过此文能对你有帮助.</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/66f8410c6bbc</p>    <p> </p>