在dispatch_async中使用"freed"self时的EXC_BAD_ACCESS

Bil*_*Kan 3 objective-c ios objective-c-blocks automatic-ref-counting 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];
    });

});
Run Code Online (Sandbox Code Playgroud)

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

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

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

Rob*_*Rob 7

您对内存管理的理解是正确的,self此块中的存在将保留,self直到调度块完成.解决方案是self在块运行时不保留,通过使用块内部的weak引用self:

__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];
    });
});
Run Code Online (Sandbox Code Playgroud)

您应该查看该块以获取任何引用self(通过直接引用ivar显式或隐式),并将其替换为weakSelf.

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

__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
        }
    });
});
Run Code Online (Sandbox Code Playgroud)

顺便说一下,另一种方法是在取消视图控制器时取消网络请求.这需要更大的改变(使用NSOperation基于GCD的方法;或者使用NSURLSessionTask基于允许您取消请求的基于方法),但这是解决此问题的另一种方法.