我最近将我的游戏迁移到了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对象)。
还在这里吗
因此,当我收到内存警告时,首先我调用TextureAtlasMananger
:中的-purge
方法
- (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)
}
我为保存计划删除的图集而创建的临时数组(我不喜欢从正在迭代的数组中删除对象)似乎稍后会被"自动释放"。
我检查过这个类似的问题:对象释放与ARC不一致,但我不明白它如何适用于我的情况。有问题的数组是该方法的局部变量,使用alloc/init创建。如何确保它不自动发布?(如果是这样的话)。
编辑(已解决?)
我可以确认延迟的解除分配消失了(即,代码按预期工作)如果我更换这个:
[atlasRank removeObjectsInArray:atlasesToRemove];
这个:
[atlasRank removeObjectsInArray:atlasesToRemove];
atlasesToRemove = nil;
您可以检查是否有任何对象被自动释放池无意中保留:只需将purge
方法的内容包装在@autorelease
块中即可。当控制流离开池的作用域时,这将删除任何最近自动发布的对象。
编辑,回答评论:
ARC没有给出关于何时将对象添加到自动发布池的确切承诺。使用优化进行编译时,生成的代码实际上有所不同。
您可以通过在自动变量(函数范围)的声明中添加objc_precise_lifetime
属性来控制它的行为。
您已经找到了答案,但需要详细说明的是:因为您在缓存中创建了对每个对象的本地引用,所以这些对象中的每个对象的生存期都至少延长到循环结束时(可能会根据ARC的判断进一步延长)。如果您将本地变量标记为__weak
,ARC将跳过这一步。