我最近将我的游戏迁移到了ARC。 首先,我注意到我的应用程序在玩了一段时间后崩溃了。所以我开始调试它并注意到,在收到内存警告时,一些资源的释放被破坏了。
背景(如果您理解以下代码,请不要阅读)
在我的游戏中,OpenGL纹理封装在Objective-C类(Texture
)中。该类跟踪“使用次数”,即在给定时间引用纹理的对象数量(类似于Obj-C retainCount
),其属性名为useCount
。在重新分配时,OpenGL纹理被破坏。
使用TextureAtlas
个对象创建精灵。每个纹理图集与纹理图像内的Texture
对象和命名子区域的数据库相关联。在创建图集时,它会将关联的Texture
的使用次数增加1.此外,每个纹理图集都会跟踪有多少Sprite
个实例引用其纹理(即,有多少精灵已经过了从地图集中创建并且仍在附近)。
不用说,在重新分配时,每个图集会将相关纹理的使用次数减少1。
此外,当创建新Sprite
时(来自TextureAtlas
或其他),相应纹理的useCount
也会增加,每个精灵一次。精灵重新分配时再次减少。
因此,只要某些Sprite
引用TextureAtlas
的纹理,就无法清除地图集。只要某些Sprite
或TextureAtlas
引用Texture
,就无法清除纹理。
Texture
个对象和TextureAtlas
个对象分别由单例TextureManager
和TextureAtlasManager
管理。这两位经理负责根据需要创建资源,并在内存不足的情况下清除未使用的资源。
我选择了这个设计(通过精灵和地图集解耦Texture
使用计数,TextureAtlas
使用精灵计数)因为有时我可能需要一个纹理而不是一个精灵(例如,一个精灵) 3D对象)。
还在吗?
因此,当我收到内存警告时,首先我调用-purge
中的TextureAtlasMananger
方法:
- (void) purge
{
// Called on Low Memory Situations.
// purges unused atlases.
// _atlasRank is an array of atlases in MRU order
// _atlasDatabase is a dictionary of atlases keyed by their name
NSUInteger count = [_atlasRank count];
NSMutableArray* atlasesToRemove = [[NSMutableArray alloc] init];
for (NSUInteger i=0; i < count; i++) {
TextureAtlas* atlas = [atlasRank objectAtIndex:i];
if ([atlas canDelete]) {
// Means there are no sprites alive that where created
// from this atlas
[atlasesToRemove addObject:atlas];
[_atlasDatabase removeObjectForKey:[atlas name]];
NSLog(@"TextureAtlasManager: Successfully purged atlas [%@]", [atlas name]);
}
else{
// Means some sprite remains that was
// created from this atlas
NSLog(@"TextureAtlasManager: Failed to purge atlas [%@]", [atlas name]);
}
}
[_atlasRank removeObjectsInArray:atlasesToRemove];
// At this point, atlasesToRemove should be deallocated
// by ARC, and every atlas in atlasesToRemove
// should be deallocated as well.
// This FAILS to delete unused textures:
[[TextureManager sharedManager] purgeUnusedTextures];
// (:Removed atlases are not yet deallocated and 'retain'
// their texture)
// ...But This SUCCEEDS:
[[TextureManager sharedManager] performSelector:@selector(purgeUnusedTextures)
withObject:nil
afterDelay:0.5];
// (i.e., -[TextureAtlas dealloc] gets called before -purgeUnusedTextures)
}
似乎我创建的用于保存计划删除的地图集的临时数组(我不喜欢从正在迭代的数组中删除对象)之后正在“自动释放”。
我已经检查了这个类似的问题:Inconsistent object deallocation with ARC?,但我没看到它如何适用于我的情况。有问题的数组是方法的局部变量,使用alloc / init创建。我怎样才能确保它不是自动释放的? (如果是这样的话)。
编辑(已解决?)
我可以确认延迟的释放消失(即代码按预期工作) 如果我替换它:
[atlasRank removeObjectsInArray:atlasesToRemove];
用这个:
[atlasRank removeObjectsInArray:atlasesToRemove];
atlasesToRemove = nil;
答案 0 :(得分:3)
您可以检查自动释放池是否无意中保留了您的任何对象:只需将purge
方法的内容包装在@autorelease
块中。当控制流离开池的范围时,这将删除任何最近自动释放的对象。
修改,回答评论:
ARC没有给出关于何时将对象添加到自动释放池的确切承诺。在使用优化进行编译时,生成的代码实际上有所不同。
您可以通过在其声明中添加objc_precise_lifetime
attribute来以某种方式控制自动变量(函数范围)的行为。
答案 1 :(得分:2)
您已经找到了答案,但要详细说明:因为您创建了对缓存中每个对象的本地引用,所以每个对象的生命周期至少延伸到循环的末尾(可能还会进一步ARC可自行决定。)如果您将局部变量标记为__weak
,ARC将跳过该步骤。