在dispatch_async中使用“freed”self时的EXC_BAD_ACCESS

时间:2014-01-05 12:14:05

标签: ios objective-c automatic-ref-counting objective-c-blocks sprite-kit

我在iOS7中使用新的SpriteKit编写了一个游戏。我有一个定制的SKSpriteNode,可以获取并显示Facebook个人资料图片。但是,因为加载图片可能需要一些时间。我初始化节点时尝试在后台加载图片,并仅在加载图片时显示。这是我写的代码片段:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{


    // Code to load Facebook Profile picture
    // ...
    SKSpriteNode *fbFrame = [SKSpriteNode spriteNodeWithTexture:facebookPicTexture];
    dispatch_async(dispatch_get_main_queue(), ^{


        // self is my customised SKSpriteNode - so here I just add a sprite node of
        // a facebook picture to myself as a child     
        [self addChild:fbFrame];
    });

});

通常工作正常。但是,如果Facebook个人资料图片的加载速度很慢,则用户可能已经在加载图片时切换到另一个屏幕。在这种情况下,self实际上将从场景层次结构中删除,并且不会对其进行引用。

当我读取块文档时,我认为异步块将保留self,因此我认为在调用主线程块时它仍然有效。事实证明,如果pic加载非常慢,并且当从层次结构中删除self时调用第二个dispatch_async,则行[self addChild:fbFrame]将发生错误的访问错误。

我是否理解块内存管理不正确?有没有办法解决这类问题?

2 个答案:

答案 0 :(得分:7)

您对内存管理的理解是正确的,此块中self的存在将保留self,直到调度块完成。解决方案是在块运行时不保留self,方法是使用块内weak的{​​{1}}引用:

self

您应该查看该块以查看对{​​{1}}的任何引用(通过直接引用ivar显式或隐式),并将其替换为__weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // do some stuff in background here; when done, then: dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf addChild:fbFrame]; }); });

或者,(在这种情况下不太可能)有时您会看到self / weakSelf模式,您必须确保在执行该嵌套块期间未释放weakSelf

strongSelf

顺便说一下,另一种方法是在取消视图控制器时取消网络请求。这需要更大的改变(使用基于self的方法而不是GCD;或者使用基于__weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // do some stuff in background here; when done, then: dispatch_async(dispatch_get_main_queue(), ^{ typeof(self) strongSelf = weakSelf; if (strongSelf) { // stuff that requires that if `self` existed at the start of this block, // that it won't be released during this block } }); }); 的方法来允许您取消请求),但这是解决此问题的另一种方法。

答案 1 :(得分:-1)

我认为你需要这样写:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);

objc_setAssociatedObject(self, @"yourTag", @"Alive", OBJC_ASSOCIATION_RETAIN);

dispatch_async(queue, ^{    
    // Code to load Facebook Profile picture
    // ...
    SKSpriteNode *fbFrame = [SKSpriteNode spriteNodeWithTexture:facebookPicTexture];
    dispatch_async(dispatch_get_main_queue(), ^{

        NSString *strAlive = (NSString *)objc_getAssociatedObject(self, @"yourTag");
        if (strAlive)
        {
            // self is my customised SKSpriteNode - so here I just add a sprite node of
            // a facebook picture to myself as a child     
            [self addChild:fbFrame];
        }
    });

});
dispatch_release(queue);

当你不希望调度继续执行过程时,自我(ViewController)不再可见,所以写下:

objc_setAssociatedObject(self, @"yourTag", nil, OBJC_ASSOCIATION_RETAIN);

在: - viewWillDisapear- viewDidDisapear 当你回到屏幕时更新调用发送。