使用ARC延迟释放包含的对象?

时间:2012-07-11 13:03:33

标签: objective-c ios automatic-ref-counting autorelease dealloc

我最近将我的游戏迁移到了ARC。 首先,我注意到我的应用程序在玩了一段时间后崩溃了。所以我开始调试它并注意到,在收到内存警告时,一些资源的释放被破坏了。

背景(如果您理解以下代码,请不要阅读)

在我的游戏中,OpenGL纹理封装在Objective-C类(Texture)中。该类跟踪“使用次数”,即在给定时间引用纹理的对象数量(类似于Obj-C retainCount),其属性名为useCount。在重新分配时,OpenGL纹理被破坏。

使用TextureAtlas个对象创建精灵。每个纹理图集与纹理图像内的Texture对象和命名子区域的数据库相关联。在创建图集时,它会将关联的Texture的使用次数增加1.此外,每个纹理图集都会跟踪有多少Sprite个实例引用其纹理(即,有多少精灵已经过了从地图集中创建并且仍在附近)。 不用说,在重新分配时,每个图集会将相关纹理的使用次数减少1。

此外,当创建新Sprite时(来自TextureAtlas或其他),相应纹理的useCount也会增加,每个精灵一次。精灵重新分配时再次减少。

因此,只要某些Sprite引用TextureAtlas的纹理,就无法清除地图集。只要某些SpriteTextureAtlas引用Texture,就无法清除纹理。

反过来,

Texture个对象和TextureAtlas个对象分别由单例TextureManagerTextureAtlasManager管理。这两位经理负责根据需要创建资源,并在内存不足的情况下清除未使用的资源。

我选择了这个设计(通过精灵和地图集解耦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;

2 个答案:

答案 0 :(得分:3)

您可以检查自动释放池是否无意中保留了您的任何对象:只需将purge方法的内容包装在@autorelease块中。当控制流离开池的范围时,这将删除任何最近自动释放的对象。

修改,回答评论:

ARC没有给出关于何时将对象添加到自动释放池的确切承诺。在使用优化进行编译时,生成的代码实际上有所不同。

您可以通过在其声明中添加objc_precise_lifetime attribute来以某种方式控制自动变量(函数范围)的行为。

答案 1 :(得分:2)

您已经找到了答案,但要详细说明:因为您创建了对缓存中每个对象的本地引用,所以每个对象的生命周期至少延伸到循环的末尾(可能还会进一步ARC可自行决定。)如果您将局部变量标记为__weak,ARC将跳过该步骤。