dealloc вызвал фоновое приложение сбоя очереди GCD, созданное с помощью ARC - PullRequest
10 голосов
/ 19 января 2012

У меня есть контроллер представления, который загружает ресурс в фоновую очередь 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 ()

Код, который я выполняю в основном потоке, выглядит следующим образом:

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

I'mсборка для 4.3, поэтому, если я использую ссылку __unsafe_unretained на себя в блоке обратного вызова, это исправит мою текущую проблему, но введет новую проблему наличия висячего указателя.

Что вы рекомендуете для разработки решения для этого?Другие обходные пути включали переопределение release, чтобы он всегда вызывался в основном потоке, но это явно не сработает в среде ARC.

Похоже, что это должно быть гораздо более распространенной проблемой с ARC и GCD, но я не могу найти что-либо в Интернете.Это только потому, что я нацелился на

Ответы [ 7 ]

5 голосов
/ 20 января 2012

ОБНОВЛЕНИЕ: нижеприведенное решение, на самом деле, не работало на 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");
    });
});

Код в основном потоке - это всего лишь уловка, чтобы убедиться, что компилятор не просто полностью оптимизирует код;Мне нужно, чтобы объект блока был наконец освобожден основным потоком.Похоже, этот код работает с уровнем оптимизации компилятора -Os.

Так что я нашел решение своей проблемы, хотя это супер-хакерский вариант.После этого исправления я не смог воспроизвести проблему, хотя я думаю, что это очень плохой архитектурный дизайн.

Напомним, проблема в том, что у меня есть блок в фоновой очереди, который разрушается.Этот блок является объектом, и он имеет сильную ссылку на блок обратного вызова, который содержит сильную ссылку на себя.Фоновый блок освобождается из фоновой очереди.Поэтому я перевел вызов в другой диспетчерский вызов в основную очередь.Итак, мой метод извлечения исходит из этого:

dispatch_async(backgroundQueue, ^{
    /* do all the things */
});

К этому:

dispatch_async(dispatch_get_main_queue(), ^{
    dispatch_async(backgroundQueue, ^{
        /* do all the things */
    });
});

Код асинхронно отправляет блок в основную очередь, а затем отправляет блок в фоновую очередь.Поскольку фоновый блок является объектом и принадлежит области видимости основной очереди, он освобождается в основном потоке, вызывая возможное освобождение UITextView, в результате чего происходит сбой в основной очереди, что также решает мою проблему.

Очевидное архитектурное решение заключается в использовании ссылки __weak на себя в моем блоке обратного вызова, но мне придется подождать, пока я не откажусь от поддержки iOS 4.3.

3 голосов
/ 09 апреля 2012

На самом деле, GCD позволяет сохранять и освобождать очереди отправки для этой цели.Фактически это задокументировано по адресу: «Управление памятью для очередей отправки» .

dispatch_retain(first_queue);
dispatch_async(a_queue, ^{
                            do_not_wait_for_me();
                            dispatch_async(first_queue, ^{ i_am_done_now(); });
                            dispatch_release(first_queue);
                         });

В вашем сценарии я бы заменил first_queue вашей основной очередью отправки.Сохраняя основную очередь отправки, вы гарантируете, что она не будет освобождена до завершения обратного вызова.

Другой пример можно найти по адресу: " Выполнение блока завершения после выполнения задачи. "

2 голосов
/ 20 января 2012

Эш,

Ваша проблема - раннее освобождение, поскольку ваши очереди разрушаются.Следовательно, прекрати делать это.Как?Ты почти там.Из фоновой очереди вы захотите СИНХРОННО возвращать данные в контроллер в главном потоке.Таким образом, когда все раскручивается, они освобождаются в безопасном порядке.Например:

dispatch_async(backgroundQueue, ^{

    /* download stuff */

    dispatch_sync(dispatch_get_main_queue(), ^{

        // Pass the data to the controller.
    });
});

Этот шаблон позволяет гарантировать правильную доставку ваших данных любому компоненту пользовательского интерфейса в главном потоке, а затем разрешает постепенный выпуск.

Эндрю

Редактироватьдля второго ответа с переменной __block:

__block UIViewController *vc = danglingVC;

dispatch_async(backgroundQueue, ^{

    /* download stuff */

    dispatch_async(dispatch_get_main_queue(), ^{

        // Pass the data to the controller.

        vc = nil;
    });
});

С ARC вы принудительно освобождаете, устанавливая слот сильного хранилища в ноль.Обратите внимание, что теперь я могу сделать передачу в основной поток асинхронной.Я думаю, что это четкое и четкое решение вашей проблемы.Это не зависит от тонкого взаимодействия между ARC, GCD и блоками.

Андрей

0 голосов
/ 17 июля 2014

Недавно я столкнулся с подобной проблемой. У меня была роскошь использовать обнуление слабых ссылок в ARC для решения этой конкретной проблемы. Однако я рассмотрел альтернативное решение, которое должно работать с MRC.

__block UIViewController *vc = ...;
[vc retain];
dispatch_async(backgroundQueue, ^{
    // ... do some things with vc
    dispatch_async(dispatch_get_main_queue(), ^{
        // ... do more things with vc
        [vc release]; // vc might be dealloc'd here but not before
    });
});

Спецификатор хранения __block в vc гарантирует, что блок не сохранит объект, указанный в vc. Контроллер представления сохраняется до тех пор, пока блок, который вызывается в главной очереди, не освободит его и, возможно, в этот момент не будет освобожден.

0 голосов
/ 03 октября 2012

Альтернативой может быть сохранение ссылки на объект в области за пределами асинхронной отправки.Например:

// create a dispatch queue
dispatch_queue_t sdq = dispatch_queue_create("com.fred.exampleQueue", NULL);

// my object references container
__block NSMutableArray *ressources = [[NSMutableArray alloc] init];

dispatch_async(sdq, ^{
    __block MyLoader *ml = [[MyAsyncLoader alloc] initWithCallback:^(id result)
    {
        NSLog(@"loader result:  %@", result);
        // since I ask for ressource in my final CallBack it's still here
        // and my loader too, I can now dispose of it.
        [ressources removeObject:ml];
    }];

    // perform async loading and call my callback when it's done...
    [ml asyncLoad];
    // save my object
    [ressources addObject:ml];
});
0 голосов
/ 20 января 2012

Вы никогда не можете гарантировать, что объект будет заблокирован в каком-либо конкретном потоке, как вы выяснили.

Лучшее решение, которое я нашел, находится в вашем dealloc, проверьте, не находитесь ли вы на главнойнить.Если нет, то выполните executeSelector в главном потоке с остальной частью dealloc и дождитесь завершения.

В качестве альтернативы, реализуйте ту же логику с блоками: если не в основном потоке, отправьте dispatch_sync в главную очередь с остальнымисделка.

0 голосов
/ 20 января 2012

Ваш анализ кажется обоснованным. Так что, возможно, конец блока может сохранить контроллер и сделать авто-выпуск основного потока (возможно, после небольшой задержки, чтобы избежать условий гонки).

...