浅析 Foreach

如果说 Objective-C 里面存在几个最容易出错的语法的话,那么 Foreach 肯定要算其中之一。在进行讨论之前,首先让我们看看下面一段代码

    NSMutableArray *array = [NSMutableArrayarrayWithObjects:@"",@"", @"", nil];

            

   for(id objectin array)

    {

        [arrayremoveObject:object];

    }

毫无疑问,以上这段代码是注定会出错的,而且错误原因一定为

Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x?????????> was mutated while being enumerated.'

出错的原因当然就是在 [array removeObject:object]; 这一句了。但是,我们在平常阅读代码的时候,甚至在自己写代码的时候有时就会不经意的来上这么一句。而且运行的时候还没有出错。原因为何?想必好多人在这个上面都是迷迷糊糊的。现在,让我们来详解一下

相比 for(initialization; condition; increment)  这样的语法,苹果声称 foreach 语法的速度基本同样快,同时要更安全。当然不必阅读汇编代码我们都知道 for 循环的结构与行为方式—— 这算是作为程序员的常识了。我们也知道为何以下的代码不会正确运行

    for(int i =0; i < [array count]; i++)

    {

        [arrayremoveObject:[array objectAtIndex:i]];

    }


原因很简单:每一次 removeObject 之后,[array count] 就会减少,所以以上的代码永远没办法把所有对象都移出数组。

那么相比起来,foreach 又如何呢?为何通过 foreach 语句移除对象的时候出现的不是移除不完全的问题而是报错?这一切都源于 foreach 这个语法的设计。

foreach 语法的设计是用于提供一个高性能,安全的 Enumeration 的简易方法。不得不说,现代程序的发展越来越向多线程靠拢,而多线程又是一件很麻烦的事情,所以苹果在不断的为我们提供诸如 GCD,NSOperation 等简单实现多线程操作的方法 —— 把生成额外线程的任务都交给了操作系统。而 foreach 在多线程使用的时候有一个最漂亮的特性:它会防止正在进行 Enumeration 的对象被修改。如果用户尝试对其进行修改的话,就会产生一个例外,也就是我们最开头所说的那个错误了。

所以我们可以这么说,苹果为了使大家能够有一个快速(如果利用了多线程则快于 for 循环),安全的枚举方法,将 foreach 设计为一个不能在循环中对主体本身进行改变的一个语法。那么自然而然,通过 foreach 语句来 removeObject 就是一个完完全全不合理的逻辑,这样的写法应该在所有代码中避免。

那么讲到这里肯定有人会问,为何有些时候我们使用 foreach 语法来 removeObject 的时候运行良好呢?比如以下这段代码

    for(id objectin array)

    {

        [arrayremoveObject:object];

       break;

    }


肯定有人经常这么写,而且没有出过错。而原因就在于,foreach 语法对于对象修改的检测是在下一次循环的时候。以上这些都可以通过 NSFastEnumeration 的协议看出,NSFastEnumeration 要求所有能够使用 foreach 语法的对象必须提供一个方法

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id__unsafe_unretained [])buffer count:(NSUInteger)len;

state 里面包含一个 *mutationsPtr 位,用以纪录修改。而我想,大多数朋友在使用 foreach 来 removeObject 的时候都是工作在单线程状态下的,所以一般不会出错。不过出于语言逻辑与程序逻辑的对应,还是不推荐经常这么做。毕竟,所谓 Error-Prone 的代码,一般都是不按照规范来才会这样的 =w=

顺带一提,foreach 语法的工作方式是预先把对象提取出来的(同可以通过协议看出),下面附上一段代码,高手可以通过汇编来进行更详细的解读(900多行哦)

int main(int argc,const char * argv[])

{

   NSMutableArray *array = [NSMutableArrayarrayWithObjects:@"1",@"2", @"3",@"4", @"5",@"6", @"7",@"8", @"9",nil];

            

   for(id objectin array)

    {

        [arrayremoveObject:object];

    }

    

   return 0;

}

如果有更多疑问的话,请参考以下这段资料

http://www.mikeash.com/pyblog/friday-qa-2010-04-16-implementing-fast-enumeration.html

谢谢!

你可能感兴趣的:(浅析 Foreach)