Что может вызвать SIGABRT для dispatch_async в iOS9.1?

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

Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Triggered by Thread:  8
OS Version:          iOS 9.1 (13B143)
Code Type:           ARM (Native)

0   libsystem_kernel.dylib          0x392ccc84 0x392b8000 + 85124
1   libsystem_pthread.dylib         0x39370732 0x3936c000 + 18226
2   libsystem_c.dylib               0x39264f9a 0x3921a000 + 307098
3   libsystem_c.dylib               0x39264f2c 0x3921a000 + 306988
4   libsystem_c.dylib               0x392447ea 0x3921a000 + 174058
5   MyApp                           0x000cb3e0 __69-[MyDataManager myMethod:]_block_invoke (MyDataManager.m:2367)

Строка 2367 просто:

2363: BOOL success = [db executeUpdate:@"INSERT INTO table (id, content) VALUES (?, ?)", message.remoteId, message.content];
2364: assert(success);
2365: DebugLog(@"DB Results %d", success);
2366: 
2367: dispatch_async(dispatch_get_main_queue(), ^{
2368:     [self cleanupMethod:args];
2369: });

Хотя в этом блоке определенно есть код, он состоит всего из 1 строки, и этот код, похоже, не выполняется в этом стеке, потому что иначе я бы увидел cleanupMethod над myMethod.

Редактировать: вы можете видеть, что непосредственно перед dispatch_async есть утверждение! Я изначально думал, что этот сбой был из-за assert. Но номера строк никогда не совпадали — утверждение было на много строк выше (строка 2364, а не 2367) — и когда я проверил его дальше, я увидел, что если утверждение сработает, мой стек не будет включать _block_invoke, которые вы можно увидеть в конце вызова myMethod.

Кто-нибудь может подсказать, как dispatch_async может вызвать такое поведение? Более того, есть ли способ символизировать код Apple в libsystem_c.dylib?

Бинарный образ libsystem_c.dylib:

0x3921a000 - 0x3927efff libsystem_c.dylib armv7  <0b5d65608e6f38448cd207fbd748d372> /usr/lib/system/libsystem_c.dylib

ПРИМЕЧАНИЕ: рассматриваемый объект является глобальным синглтоном, моим «менеджером данных», если хотите. Он обрабатывает сетевые запросы и сохраняет состояние, которое может потребоваться для совместного использования между UIViewControllers. Первоначально он объявлен следующим образом:

+ (MyDataManager *)mainStore {
    static dispatch_once_t once;
    static id sharedInstance;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] init];
    });

    return sharedInstance;
}

Я понимаю последствия освобождения объекта при вызове моего метода cleanupMethod:args... но я думал, что мой глобальный синглтон всегда будет поблизости и, следовательно, его всегда можно безопасно вызывать так, как я это делаю в своем коде. ? Кроме того, я не беспокоюсь о циклах сохранения, поскольку, опять же, это должен быть глобальный синглтон.

Подходит ли этот пример кода ниже?

@interface MyDataManager
@end

@implementation MyDataManager

+ (MyDataManager *)mainStore {
    static dispatch_once_t once;
    static id sharedInstance;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] init];
    });

    return sharedInstance;
}

- (void)myMethod {
    NSDictionary *args = @{...}
    ...
    dispatch_async(dispatch_get_main_queue(), ^{
        [self cleanupMethod:args];
    });
}

- (void)cleanupMethod:(id)args {
   ...
}

@end

@interface MyViewController : UIViewController
@end

@implementation MyViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   [[MyDataManager sharedInstance] myMethod];
}

@end

person esilver    schedule 25.10.2015    source источник
comment
Было бы полезно, если бы вы отредактировали свой вопрос, включив в него содержимое блока или хотя бы несколько строк после строки 2367 файла MyDataManager.m.   -  person rob mayoff    schedule 25.10.2015
comment
Похоже, что ваш self освобождается во время срабатывания блока   -  person Inder Kumar Rathore    schedule 25.10.2015
comment
Поскольку нет специального объявления self в свидетельстве, блок должен иметь сильную ссылку на self и, таким образом, предотвращать его освобождение.   -  person rob mayoff    schedule 25.10.2015
comment
Какая версия ОС указана в отчете о сбое (в верхней части файла)? Например. iOS 9.0.2 (13A452). Что такое тип кода? Например. ARM-64 (Native). Какой двоичный образ указан для libsystem_c.dylib (ближе к концу файла)? Например. 0x199098000 - 0x199118fff libsystem_c.dylib arm64 <5052939437823b09a7b068807808eff2> /usr/lib/system/libsystem_c.dylib   -  person rob mayoff    schedule 25.10.2015
comment
Это только мое предположение, вы можете включить zombie из прибора и проверить, что происходит на самом деле.   -  person Inder Kumar Rathore    schedule 25.10.2015
comment
Я бы ослабил себя, а затем усилил, чтобы убедиться, что цикл сохранения не будет создан, что может вызвать проблемы с отладкой.   -  person Julian    schedule 25.10.2015
comment
@robmayoff Хорошо, я добавил информацию об ОС и двоичном образе из libsystem_c.dylib.   -  person esilver    schedule 25.10.2015
comment
Вы в итоге исправили это @esilver?   -  person A O    schedule 13.01.2016
comment
В итоге я переписал этот раздел и добавил тонну диагностической информации... и затем стек сбоя исчез. Итак, в общем, нет, я не решил это.   -  person esilver    schedule 13.01.2016
comment
Я вижу проблему, о которой вы говорите, я понятия не имею, как self в таком контексте мог быть выпущен, и теперь мне тоже очень любопытно. Я уверен, что вы тоже уже все перепробовали. Не уверен, говорил ли ты это где-нибудь, но помнишь ли ты, пробовали ли вы когда-нибудь вызывать -cleanUpMethod синхронно; без переноса вызова в dispatch_async?   -  person A O    schedule 13.01.2016


Ответы (1)


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

__weak typeof(self)weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
    [weakSelf cleanupMethod:args];
});

Кто-то скажет, что вам также придется использовать __strong typeof(weakSelf)strongSelf = weakSelf; здесь, чтобы сделать сильную ссылку на слабую ссылку, чтобы избежать цикла сильной ссылки, но сохранить self живым, но я бы предпочел не делать этого здесь, и в случае, если self будет быть nil к моменту выполнения блока - сообщения для nil отлично подходят для target-c, так что ничего не произойдет.

Плюс трассировка стека того, что происходит перед строкой

0x000cb3e0 __69-[MyDataManager myMethod:]_block_invoke (MyDataManager.m:2367)

определенно поможет диагностировать проблему.

РЕДАКТИРОВАТЬ: Ну, похоже, вы не используете метод класса mainStore при доступе к вашему общему объекту. Может быть, это проблема.

person Soberman    schedule 25.10.2015
comment
1. Пожалуйста, объясните, почему вы считаете, что отправка сообщения в слабую переменную всегда уместна, если вы не уверены, что кто-то еще имеет сильную ссылку (в этом случае использование слабой ссылки бессмысленно). 2. myMethod отсутствует в стеке вызовов. Это блок внутри сообщения (скорее всего, блок, вызывающий метод очистки). - person gnasher729; 25.10.2015
comment
1. Именно из-за этой ситуации у ОП. Приложение не будет аварийно завершать работу после отправки сообщения на слабую ссылку. 2. Да, насчет этого вы правы, разницы в названии не заметил. - person Soberman; 25.10.2015
comment
@Soberman Большое спасибо за ваши мысли здесь! Я добавил более подробную информацию о своем коде - мне не ясно, является ли это проблемой сильной/слабой ссылки. Можете ли вы взглянуть на мой расширенный вопрос и LMK, если вы думаете, что это все еще может быть виновником? Благодарю вас! - person esilver; 25.10.2015
comment
@Soberman Хорошо, верно, но разве self и [MyDataManager mainStore] не должны всегда относиться к одному и тому же значению? Я только что добавил assert(self == [MyDataManager mainStore]); вверху своего блока, и он проходит... - person esilver; 25.10.2015
comment
Нет. Потому что у вас может быть неинициализированный self. Но вы не используете там self, вы должны использовать метод класса. Я предполагаю, что в вашем viewDidLoad вы напрямую обращаетесь к sharedInstance, пока он не инициализируется. Вы должны использовать свой метод класса, иначе какой в ​​этом смысл. - person Soberman; 25.10.2015
comment
Извините, я все еще в замешательстве. Я считаю, что все клиенты этого синглтона должны использовать [MyDataManager mainStore]. Сам синглтон всегда может использовать либо self, либо [MyDataManager mainStore]. Если вы говорите, что это неправда, для меня это новая информация... возможно, мне следует открыть новую С.О. сообщение по этому конкретному вопросу? Кроме того, что означает, что self не инициализирован, если изначально он указывает на [MyDataManager mainStore]? - person esilver; 25.10.2015
comment
Я говорил о другой части вашего кода — о viewDidLoad. - person Soberman; 25.10.2015
comment
Просто попробуйте использовать там метод класса и попытайтесь воспроизвести сбой. - person Soberman; 25.10.2015