[英]EXC_BAD_ACCESS when using “freed” self in dispatch_async
I have a game written using the new SpriteKit in iOS7. 我在iOS7中使用新的SpriteKit编写了一个游戏。 I have a customised
SKSpriteNode
which would fetch and display a Facebook profile picture. 我有一个定制的
SKSpriteNode
,可以获取并显示Facebook个人资料图片。 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.
但是,如果Facebook个人资料图片的加载速度很慢,则用户可能已经在加载图片时切换到另一个屏幕。 In such case,
self
will actually be removed from the scene hierarchy and no reference will be made to it. 在这种情况下,实际上将从场景层次结构中删除
self
并且不会对其进行引用。
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. 当我读取块文档时,我认为异步块将保留
self
,因此我认为在调用主线程块时它仍然有效。 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]
. 事实证明,如果pic加载非常慢,并且当从层次结构中删除self时调用第二个dispatch_async,则会在行
[self addChild:fbFrame]
发生错误的访问错误。
Am I understand the block memory management incorrectly? 我是否理解块内存管理不正确? And is there a way to solve that kind of problem?
有没有办法解决这类问题?
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. 您对内存管理的理解是正确的,此块中
self
的存在将保留self
直到调度块完成。 The solution is to not retain self
while the block runs, by using a weak
reference to self
inside the block: 解决方案是在块运行时不保留
self
,方法是在块内使用对self
的weak
引用:
__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
. 您应该查看该块以获取对
self
任何引用(通过直接引用ivar显式或隐式),并将其替换为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: 或者,(在这种情况下不太可能)有时您会看到
weakSelf
/ strongSelf
模式,您必须确保在执行该嵌套块期间不释放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
}
});
});
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. 这需要更大的改变(使用基于
NSOperation
的方法而不是GCD;或者使用基于NSURLSessionTask
的方法来允许您取消请求),但这是解决此问题的另一种方法。
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: 当你不希望调度继续执行过程时,自我(ViewController)不再可见,所以写下:
objc_setAssociatedObject(self, @"yourTag", nil, OBJC_ASSOCIATION_RETAIN);
In: - viewWillDisapear
Or - viewDidDisapear
And renew calling the dispatch when you come back to than screen. 在:
- viewWillDisapear
或- viewDidDisapear
当你回到屏幕时更新调用调度。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.