EXC_BAD_ACCESS when using "freed" self in dispatch_async

I have a game written using the new SpriteKit in iOS7. I have a customised SKSpriteNode which would fetch and display a Facebook profile picture. However, since it may take some time to load the picture. I tried to load the picture in background when I initialised the node and display it only when the picture is loaded. Here is code snippet I wrote:

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];
    });

});

It works fine normally. However, if the loading of Facebook profile pic is slow, the user may have already switch to another screen when the picture is loaded. In such case, self will actually be removed from the scene hierarchy and no reference will be made to it.

When I read the block doc, I think the async block will retain self and so I presume it will still be valid when the main thread block is called. It turns out from time to time if the pic loading is really slow and the second dispatch_async is called when self is removed from the hierarchy, a bad access error will occur at the line [self addChild:fbFrame].

Am I understand the block memory management incorrectly? And is there a way to solve that kind of problem?

Answers


Your understanding of the memory management is correct, that the presence of self in this block will retain self until the dispatched block is completed. The solution is to not retain self while the block runs, by using a weak reference to self inside the block:

__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];
    });
});

You should review that block for any references to self (either explicit or implicitly by referencing an ivar directly), and replace them with weakSelf.

Or, (unlikely in this case) sometimes you'll see the weakSelf/strongSelf pattern where you must ensure that self isn't released during the execution of that nested block:

__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
        }
    });
});

By the way, the other approach is to cancel the network request when the view controller is dismissed. This requires a more significant change (using a NSOperation-based approach rather than GCD; or use NSURLSessionTask-based approach that allows you to cancel the request), but it is the other way to tackle this.


I think you need to write like this:

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);

When you dont want the dispatch to continue procedure when the self (ViewController) is not visible anymore so write this:

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

In: - viewWillDisapear Or - viewDidDisapear And renew calling the dispatch when you come back to than screen.


Need Your Help

Visual Studio 2012: exclude compiled file from linking

visual-studio-2012 build linker .obj

I have a simple C language project with a few files being compiled. These are compiled to .obj files and then linked together. So far so good, now I need to exclude one of these files from linking....

Joining SQL tables to compare revenue vs expense

sql sql-server join

Let me say first that I'm new to SQL, and learning much every day. With that said, here is my problem. I have a view that is already created (It shows revenue generated on equipment), but I need on...