Подтвердить что ты не робот

Objective-C авария на __destroy_helper_block_

У меня есть приложение iOS, которое разбивается на вызовы типа __destroy_helper_block_253 и __destroy_helper_block_278, и я не совсем уверен, что ссылается либо на "destroy_helper_block", либо на номер после того, как он должен указывать на.

Есть ли у кого-нибудь указатели на то, как отслеживать, где именно могут произойти эти сбои?

Вот пример трассировки (обратите внимание, что строки с __destroy_helper_block ссылаются только на файл, в котором он содержится, и ничего больше, когда обычно включается номер строки).

Thread : Crashed: com.apple.root.default-priority
0  libdispatch.dylib              0x000000018fe0eb2c _dispatch_semaphore_dispose + 60
1  libdispatch.dylib              0x000000018fe0e928 _dispatch_dispose + 56
2  libdispatch.dylib              0x000000018fe0e928 _dispatch_dispose + 56
3  libdispatch.dylib              0x000000018fe0c10c -[OS_dispatch_object _xref_dispose] + 60
4  Example App                    0x00000001000fe5a4 __destroy_helper_block_278 (TSExampleApp.m)
5  libsystem_blocks.dylib         0x000000018fe53908 _Block_release + 256
6  Example App                    0x00000001000fda18 __destroy_helper_block_253 (TSExampleApp.m)
7  libsystem_blocks.dylib         0x000000018fe53908 _Block_release + 256
8  libdispatch.dylib              0x000000018fe0bfd4 _dispatch_client_callout + 16
9  libdispatch.dylib              0x000000018fe132b8 _dispatch_root_queue_drain + 556
10 libdispatch.dylib              0x000000018fe134fc _dispatch_worker_thread2 + 76
11 libsystem_pthread.dylib        0x000000018ffa16bc _pthread_wqthread + 356

Изменить 1: Здесь приведен пример одного из блоков, определенных в файле, где произошел сбой (с измененным кодом приложения).

- (void)doSomethingWithCompletion:(void (^)())completion {
    void (^ExampleBlock)(NSString *) = ^{
        NSNotification *notification = [NSNotification notificationWithName:kExampleNotificationName object:nil userInfo:nil];
        [[NSNotificationCenter defaultCenter] postNotification:notification];

        if (completion) {
            completion();
        }
    };

    // Async network call that calls ExampleBlock on either success or failure below...
}

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

Изменить 2: добавлено больше контекста для вышеописанной функции.

4b9b3361

Ответ 1

Каждый кадр трассировки стека должен дать вам представление о том, что делает libDispatch, чтобы вызвать сбой. Работаем снизу:

11 libsystem_pthread.dylib        0x000000018ffa16bc _pthread_wqthread
10 libdispatch.dylib              0x000000018fe134fc _dispatch_worker_thread2 + 76

Эти две функции разворачивают рабочий поток и запускают его. В этом процессе он также создает пул автозапуска для потока.

9  libdispatch.dylib              0x000000018fe132b8 _dispatch_root_queue_drain + 556

Эта функция сигнализирует о начале процесса уничтожения очереди. Пул авторезистов, зависящий от потока, сливается, и в процессе освобождаются все переменные, на которые ссылается эта конкретная очередь. Поскольку это libDispatch, это означает, что основной объект mach и рабочий блок, который вы отправили, должны идти...

7  libsystem_blocks.dylib         0x000000018fe53908 _Block_release + 256
6  Example App                    0x00000001000fda18 __destroy_helper_block_253 (TSExampleApp.m)
5  libsystem_blocks.dylib         0x000000018fe53908 _Block_release + 25
4  Example App                    0x00000001000fe5a4 __destroy_helper_block_278 (TSExampleApp.m)

что и есть то, что здесь происходит. Номер 7 является внешним блоком, и поскольку он содержит нетривиальный объект для уничтожения (еще один блок), компилятор сгенерировал деструктор (__destroy_helper_block_253), чтобы избавиться от этого внутреннего блока. Применяя ту же линию логики, мы можем сделать вывод, что внутренний блок имеет еще один бит нетривиального уничтожения.

3  libdispatch.dylib              0x000000018fe0c10c -[OS_dispatch_object _xref_dispose] + 60
2  libdispatch.dylib              0x000000018fe0e928 _dispatch_dispose + 56
1  libdispatch.dylib              0x000000018fe0e928 _dispatch_dispose + 56

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

0  libdispatch.dylib              0x000000018fe0eb2c _dispatch_semaphore_dispose + 60

Без освобождения семафора, mach будет жаловаться, чтобы не возвращать KERN_SUCCESS при уничтожении семафора, что является фатальной ошибкой в ​​libDispatch. Фактически, в этом случае это будет abort() - технически __builtin_trap(), но они достигают той же цели. Поскольку нет отладочного приложения, вниз идет ваше приложение.

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

Ответ 2

Здесь не так много, но мое подозрение состоит в том, что блок никогда не перемещается в кучу. Блоки создаются по стеку по умолчанию. Компилятор часто может выяснить, когда переместить их в кучу, но способ, которым вы передаете этот блок из блока, вероятно, никогда не будет этого делать.

Я бы добавил completionCopy = [completion copy], чтобы заставить его на кучу. Затем работайте с completionCopy. См. bbum answer относительно хранения блоков в словарях. С ARC вам больше не нужно называть Block_copy() и Block_release(), но я думаю, что вы все еще хотите вызвать -copy здесь.

Ответ 3

Гипотеза:

  • doSomethingWithCompletion: создает ExampleBlock.
  • Вы запускаете асинхронную работу сети.
  • doSomethingWithCompletion: возвращается, а ExampleBlock освобождается.
  • Операция асинхронной сети завершается и вызывает ExampleBlock.

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

3 возможных решения:

1. Храните блок в свойстве

Сохраните блок в свойстве:

@property (nonatomic, copy) returnType (^exampleBlock)(parameterTypes);

Затем в коде

self.exampleBlock = …

Одна из проблем с этим подходом заключается в том, что вы можете иметь только один ExampleBlock.

2. Храните блок в массиве

Чтобы обойти эту проблему, вы можете хранить блоки в коллекции (например, NSMutableArray):

@property (nonatomic, strong) NSMutableArray *blockArray;

затем в коде:

self.blockArray = [NSMutableArray array];

// Later on…
[self.blockArray addObject:exampleBlock];

Вы можете удалить блок из массива, когда он ОК, чтобы освободить его.

3. Работайте над проблемой хранения, просто передав блок вокруг

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

В качестве альтернативы вы можете использовать NSBlockOperation для асинхронного кода и установить его completionBlock для готового ответа кода и добавить его в NSOperationQueue.

Ответ 4

Я думаю, что завершение будет выпущено в вашем асинхронном вызове, который может вызвать сбой.

Ответ 5

Я подозреваю, проблема не в вашем коде, а в другом месте.

Возможна одна из следующих проблем:

IFF есть объекты UIKit, которые фиксируются в блоке completion, возможно, вы получаете тонкую ошибку, когда блок выполняется на основном потоке, и этот блок сохраняет последнюю сильную ссылку на эти объекты UIKit:

Когда блок completion заканчивается, он блокируется и освобождается, и вместе с этим все импортированные переменные становятся "уничтоженными", что означает, что в случае сохраняемых указателей они получают сообщение release. Если это была последняя сильная ссылка, захваченный объект будет освобожден, что произойдет в не основном потоке - и это может быть фатальным для объектов UIKit.

Ответ 6

Я не вижу ничего плохого в отправленном коде и думаю, что ошибка находится где-то в другом месте.

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

Почему бы вам не начать с перевода кода в ExampleBlock непосредственно в блок completion?

Ответ 7

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

- (void)doSomethingWithCompletion:(void (^)())completion {
    void (^ExampleBlock)(NSString *) = [^{
        NSNotification *notification = [NSNotification notificationWithName:kExampleNotificationName object:nil userInfo:nil];
        [[NSNotificationCenter defaultCenter] postNotification:notification];

        if (completion) {
            completion();
        }
    } copy];

    // Async network call that calls ExampleBlock on either success or failure below...
}