dealloc在使用ARC构建的后台GCD队列崩溃应用程序上调用

Ash*_*row 10 memory-management objective-c grand-central-dispatch ios automatic-ref-counting

我有一个视图控制器,可以在后台GCD队列中下载资产.我将下载函数传递给回调块,以便在下载完成后执行,并且它总是在主线程上执行此块.

如果我的视图控制器在下载完成之前被用户解雇,则会出现此问题.我怀疑发生了什么,一旦我的视图控制器被解除,回调块是唯一保留对控制器的强引用的东西.回调块仅保留在后台线程中,因此一旦释放,回调块范围内捕获的所有对象也将被释放,尽管在后台队列中.

这就是问题:在后台队列中释放会导致dealloc在同一队列中运行,而不是在主队列中运行.反过来,这会dealloc在后台调用,应用程序崩溃:

2012-01-19 12:47:36.349 500px iOS[4892:12107] bool _WebTryThreadLock(bool), 0x306c10: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
[Switching to process 16643 thread 0x4103]
[Switching to process 16643 thread 0x4103]
(gdb) where
#0  0x307fd3c8 in _WebTryThreadLock ()
#1  0x307ff1b0 in WebThreadLock ()
#2  0x33f7865e in -[UITextView dealloc] ()
#3  0x0005d2ce in -[GCPlaceholderTextView dealloc] (self=0x309010, _cmd=0x340ce6b8) at /Users/ash/Dropbox/500px/500px-ios/500px iOS/500px iOS/GCPlaceholderTextView.m:113
#4  0x337cac42 in -[NSObject(NSObject) release] ()
#5  0x33dee5f4 in -[UIView(Hierarchy) removeFromSuperview] ()
#6  0x33e836cc in -[UIScrollView removeFromSuperview] ()
#7  0x33f762f0 in -[UITextView removeFromSuperview] ()
#8  0x33e01de2 in -[UIView dealloc] ()
#9  0x337cac42 in -[NSObject(NSObject) release] ()
#10 0x33dee5f4 in -[UIView(Hierarchy) removeFromSuperview] ()
#11 0x33e01de2 in -[UIView dealloc] ()
#12 0x33f437e4 in -[UIScrollView dealloc] ()
#13 0x337cac42 in -[NSObject(NSObject) release] ()
#14 0x33dee5f4 in -[UIView(Hierarchy) removeFromSuperview] ()
#15 0x33e836cc in -[UIScrollView removeFromSuperview] ()
#16 0x33e01de2 in -[UIView dealloc] ()
#17 0x337cac42 in -[NSObject(NSObject) release] ()
#18 0x33dee5f4 in -[UIView(Hierarchy) removeFromSuperview] ()
#19 0x33e01de2 in -[UIView dealloc] ()
#20 0x337cac42 in -[NSObject(NSObject) release] ()
#21 0x33e5a00e in -[UIViewController dealloc] ()
#22 0x00035f16 in -[PXPhotoViewController dealloc] (self=0x5158d0, _cmd=0x340ce6b8) at /Users/ash/Dropbox/500px/500px-ios/500px iOS/500px iOS/PXPhotoViewController.m:118
#23 0x337cac42 in -[NSObject(NSObject) release] ()
#24 0x337e5046 in sendRelease ()
#25 0x331fc92e in _Block_object_dispose ()
#26 0x0003c33a in __destroy_helper_block_ () at /Users/ash/Dropbox/500px/500px-ios/500px iOS/500px iOS/PXPhotoViewController.m:878
#27 0x331fc88e in _Block_release ()
#28 0x331fc91c in _Block_object_dispose ()
#29 0x000c8d32 in __destroy_helper_block_ () at /Users/ash/Dropbox/500px/500px-ios/500px iOS/500px iOS/PXPhotoFetcher.m:557
#30 0x331fc88e in _Block_release ()
#31 0x35eec8ec in _dispatch_call_block_and_release ()
#32 0x35ee2de2 in _dispatch_queue_drain ()
#33 0x35ee2f32 in _dispatch_queue_invoke ()
#34 0x35ee24f2 in _dispatch_worker_thread2 ()
#35 0x34ecb590 in _pthread_wqthread ()
#36 0x34ecbbc4 in start_wqthread ()
Run Code Online (Sandbox Code Playgroud)

我在主线程上执行的代码如下所示:

[[PXPhotoFetcher sharedPXPhotoFetcher] fetchPhotoDetailsWithPriority:PhotoRequestLowPriority 
    withCallback:^(PXPhotoModel *thePhotoModel) {
        // a callback function which captures self in its scope
    } forModel:model];
Run Code Online (Sandbox Code Playgroud)

我正在构建4.3,所以如果我在回调块中使用__unsafe_unretained引用self,这将解决我当前的问题但引入了悬挂指针的新问题.

您为此构建解决方案的建议是什么?其他解决方法包括覆盖,release以便始终在主线程上调用它,但这显然无法在ARC环境中工作.

这似乎应该是ARC和GCD的一个更常见的问题,但我在网上找不到任何东西.是因为我的目标是<iOS 5并且不能使用弱引用?

Ash*_*row 5

更新:事实上,下面的解决方案并没有在iOS 4上运行.由于某种原因它可以工作5,但不是4,所以我提出了一个更好的解决方案.

问题是由于块在后台被破坏引起的,所以我将它放入一个局部变量中,并在后台块中调用它,然后异步将它传递给主线程上的一个块,以便在那里释放它.它也很混乱,但看起来如下:

void(^block)(void) = ^{/*do all the things*/};

dispatch_async(queue, ^{

    block();

    dispatch_async(dispatch_get_main_queue(), ^{
        if ([block isKindOfClass:[NSString class]])
            NSLog(@"Whoa, bro");
    });
});
Run Code Online (Sandbox Code Playgroud)

主线程上的代码只是一个技巧,以确保编译器不只是完全优化代码; 我需要主线程最后释放块对象.此代码似乎与-Os编译器优化级别一起使用.

所以我想出了一个解决我的问题的方法,虽然这是超级hacky.自从这次修复以来,我一直无法重现这个问题,尽管我认为这是一个非常糟糕的建筑设计.

回顾一下,问题是我在后台队列中有一个被销毁的块.该块是一个对象,它拥有对回调块的强引用,该回调块包含对self的强引用.后台块正从后台队列中释放.所以我所做的就是在另一个调度调用中将调用包装到主队列中.所以我的fetch方法就是这样的:

dispatch_async(backgroundQueue, ^{
    /* do all the things */
});
Run Code Online (Sandbox Code Playgroud)

对此:

dispatch_async(dispatch_get_main_queue(), ^{
    dispatch_async(backgroundQueue, ^{
        /* do all the things */
    });
});
Run Code Online (Sandbox Code Playgroud)

代码异步地将块调度到主队列,然后主队列将块调度到后台队列.由于后台块是一个对象并且属于主队列的作用域,因此它在主线程上释放,导致UITextView的最终解除分区导致崩溃发生在主队列上,以及解决我的问题.

明显的架构解决方案是__weak在我的回调块中使用对self 的引用,但是我必须等到我放弃对iOS 4.3的支持.