使用Objective-C中foreach循环的一大注意事项
2012-02-12 11:43
323 查看
原文地址:/article/8668270.html
前两天忙于做老师布置的新任务——制作一款iPhone上的“雷电”,做的还算成功,玩起来颇有乐趣(可能由于是自己的劳动成果吧)。
在制作该款小游戏时我的任务之一是对子弹等需要大量产生并销毁的对象进行管理。之所以要管理这些对象是由于:如果只是简单的在发射子弹时分配内存并初始化子弹对象,在子弹消失时(飞出屏幕或达到对方)销毁此对象,开销会非常大——因为在Objective-C中对象的内存都是动态分配的(用malloc),但动态分配内存有可能非常慢(因为分配时有很多复杂的情况,比如说你请求内存时操作系统有可能先把把一些内存碎片整合起来再交给你)。对于来得快去的也快的子弹对象,我们应该采用的处理办法是建立一个“子弹对象池”,当子弹应该被销毁时并不把它的内存释放,而是把它放到“子弹对象池”中,以后需要子弹时我们就从此对象池中取出一个对象重新初始化即可,不必为它重新分配内存了。
构建管理这些子弹对象的池需要用到能动态增长的数组,NSMutableArray是首选,而且Objective-C中有方便的foreach循环,我也就毫不客气的使用了。结果就有了如下代码片断:
// 遍历正在活动的子弹对象池
for(Bullet* bullet in activeBulletPool)
{
// 如果子弹撞到了敌人,应该被移出活动子弹对象池,放入不活动子弹对象池(炸毁)
if([bullet collidesWithSomething])
{
[activeBulletPool removeObject:bullet];
[deactiveBulletPool addObject:bullet];
}
}
结果运行时会报如下错误:Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <NSCFArray: 0x3906590> was mutated while being enumerated.'。
从字面上看意思是在遍历一个集合时该集合被改变了,感觉甚是不解,于是请来粟帮忙一起调试,还是他比较敏锐,认为在foreach循环里改变正在被遍历的数组似乎不是很合适,于是改代码为:
// 遍历正在活动的子弹对象池
for(int i = 0; i < activeBulletPool; ++i)
{
Bullet* bullet = [activeBulletPool objectAtIndex:i];
// 如果子弹撞到了敌人,应该被移出活动子弹对象池,放入不活动子弹对象池(炸毁)
if([bullet collidesWithSomething])
{
[activeBulletPool removeObject:bullet];
--i;
[deactiveBulletPool addObject:bullet];
}
}
果然就好了,在佩服粟的敏锐之时我也仔细分析了一下问题的本质原因:应该是Objective-C中的foreach循环与Java中的相似,在内部是用iterator(迭代器)实现遍历的。而不管是在Java还是C++中,一旦修改了被遍历对象,在修改前生成的iterator都会失效,所以《C++ Primer》及Java课本中曾警告过不要在用iterator遍历集合时增删集合元素,看来Objective-C中也是一样。
看书时见过好多次警告竟然还犯这样的错误,写此文章好让自己长长记性。
前两天忙于做老师布置的新任务——制作一款iPhone上的“雷电”,做的还算成功,玩起来颇有乐趣(可能由于是自己的劳动成果吧)。
在制作该款小游戏时我的任务之一是对子弹等需要大量产生并销毁的对象进行管理。之所以要管理这些对象是由于:如果只是简单的在发射子弹时分配内存并初始化子弹对象,在子弹消失时(飞出屏幕或达到对方)销毁此对象,开销会非常大——因为在Objective-C中对象的内存都是动态分配的(用malloc),但动态分配内存有可能非常慢(因为分配时有很多复杂的情况,比如说你请求内存时操作系统有可能先把把一些内存碎片整合起来再交给你)。对于来得快去的也快的子弹对象,我们应该采用的处理办法是建立一个“子弹对象池”,当子弹应该被销毁时并不把它的内存释放,而是把它放到“子弹对象池”中,以后需要子弹时我们就从此对象池中取出一个对象重新初始化即可,不必为它重新分配内存了。
构建管理这些子弹对象的池需要用到能动态增长的数组,NSMutableArray是首选,而且Objective-C中有方便的foreach循环,我也就毫不客气的使用了。结果就有了如下代码片断:
// 遍历正在活动的子弹对象池
for(Bullet* bullet in activeBulletPool)
{
// 如果子弹撞到了敌人,应该被移出活动子弹对象池,放入不活动子弹对象池(炸毁)
if([bullet collidesWithSomething])
{
[activeBulletPool removeObject:bullet];
[deactiveBulletPool addObject:bullet];
}
}
结果运行时会报如下错误:Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <NSCFArray: 0x3906590> was mutated while being enumerated.'。
从字面上看意思是在遍历一个集合时该集合被改变了,感觉甚是不解,于是请来粟帮忙一起调试,还是他比较敏锐,认为在foreach循环里改变正在被遍历的数组似乎不是很合适,于是改代码为:
// 遍历正在活动的子弹对象池
for(int i = 0; i < activeBulletPool; ++i)
{
Bullet* bullet = [activeBulletPool objectAtIndex:i];
// 如果子弹撞到了敌人,应该被移出活动子弹对象池,放入不活动子弹对象池(炸毁)
if([bullet collidesWithSomething])
{
[activeBulletPool removeObject:bullet];
--i;
[deactiveBulletPool addObject:bullet];
}
}
果然就好了,在佩服粟的敏锐之时我也仔细分析了一下问题的本质原因:应该是Objective-C中的foreach循环与Java中的相似,在内部是用iterator(迭代器)实现遍历的。而不管是在Java还是C++中,一旦修改了被遍历对象,在修改前生成的iterator都会失效,所以《C++ Primer》及Java课本中曾警告过不要在用iterator遍历集合时增删集合元素,看来Objective-C中也是一样。
看书时见过好多次警告竟然还犯这样的错误,写此文章好让自己长长记性。
相关文章推荐
- 使用Objective-C中foreach循环的一大注意事项
- 使用Objective-C中foreach循环的一大注意事项
- java中foreach循环的注意事项
- THinkPHP在模板中的volist循环使用外部变量注意事项
- 使用while循环的注意事项:
- PHP中foreach循环中使用引用要注意的地方
- 使用foreach循环要注意的
- shell中while循环里使用ssh的注意事项
- 循环使用summit调用另外一个报表时,注意事项
- PHP中foreach循环中使用引用要注意的地方
- [循环队列]使用原因与注意事项
- Objective - C基础: 第二天 - 7.self使用注意事项
- Objective-C类方法中使用self注意事项
- 循环中使用Random注意事项
- for循环当中使用定时器的注意事项
- 使用C、C++、Objective-C三语言混编时的注意事项
- Objective-C类方法中使用self注意事项
- Objective-C语言中隐式循环与需要注意的事项
- 使用循环语句应该注意的事项