Убийственная связка из NSCache и UINib

Убийственная связка из NSCache и UINib

564

Фактически, неувязка наблюдалась уже достаточно издавна, но тогда наше приложение еще употребляло TestFlight и инфы для анализа не доставало. Желаю поделиться еще одним крешем, с которым разбирался пару месяцев назад. Креш характеризовался приблизительно таковым стеком: На данный момент, по прошествии времени, крешрепорты такового типа больше не наблюдаются в HockeyApp , а ранее были одними из самых фаворитных.

Thread 0 Crashed:
0 libobjc.A.dylib 0x39abcf42 objc_msgSend + 2
1 CoreFoundation 0x2bfe0c61 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 10
2 CoreFoundation 0x2bf3c6d5 _CFXNotificationPost + 1782
3 Foundation 0x2cc6e129 -[NSNotificationCenter postNotificationName:object:userInfo:] + 70
4 Foundation 0x2cc72c8f -[NSNotificationCenter postNotificationName:object:] + 28
5 UIKit 0x2f750883 -[UIApplication _performMemoryWarning] + 132
6 libdispatch.dylib 0x3a0107a7 _dispatch_client_callout + 20
7 libdispatch.dylib 0x3a021253 _dispatch_source_latch_and_call + 624
8 libdispatch.dylib 0x3a0122ed _dispatch_source_invoke + 210
9 libdispatch.dylib 0x3a013e1f _dispatch_main_queue_callback_4CF + 328
10 CoreFoundation 0x2bfee3b1 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 6
11 CoreFoundation 0x2bfecab1 __CFRunLoopRun + 1510
12 CoreFoundation 0x2bf3a3c1 CFRunLoopRunSpecific + 474
13 CoreFoundation 0x2bf3a1d3 CFRunLoopRunInMode + 104
14 GraphicsServices 0x332cf0a9 GSEventRunModal + 134
15 UIKit 0x2f5487b1 UIApplicationMain + 1438
16 xxx 0x0015bb81 main (main.m:18)
17 libdyld.dylib 0x3a030aaf start + 0
Но проверка по коду проекта не выявила подозрительных ситуаций — все, кто употреблял эту нотификацию, или были синглтонами, или наиболее-наименее корректно отписывались. По всей видимости, какой-то объект подписался на UIApplicationDidReceiveMemoryWarningNotification и запамятовал отписаться перед своим ликвидированием. На тот момент дело сиим и ограничилось, идей для фикса пока что не было. По вызову -[UIApplication _performMemoryWarning] понятно, что неувязка произошла при обработке memory warning.

Потом, когда Apple купила TestFlight, мы перебежали на HockeyApp. Они употребляют крутую крешрепортилку (PLCrashReporter), и в целом работа с крешами там обстояла еще лучше (можно еще и свои логи/инфу аттачить при посылке репорта с аксессуара). Но, ворачиваясь к дилемме, в добавок к стеку, приведенному выше, возникли еще и такие строки:

Application Specific Information:
objc_msgSend() selector name: setArchiveData:
Сейчас мы знаем, какой селектор посылался умершему объекту. Отписка от нотификации происходит в деаллоке UINib. Это приватный класс, и с помощью свизлинга его способов лицезреем, что он создается и держится UINib’ами. В нашем коде таковых способов/параметров не присутствовало, что подтверждало прежний анализ. вызывает setArchiveData) — этот вызов и падает в крешлоге. Соответственно, встает задачка отыскать класс, у которого есть таковой селектор. В этом помогают функции obj-c рантайма objc_getClassList (выдает перечень зарегистрированных классов) и class_copyMethodList (дозволяет получить способы экземпляров и самого класса). Далее, снова же с помощью свизлинга и дизассемблирования, выясняем, что UINib подписывается на UIApplicationDidReceiveMemoryWarningNotification, и при ее получении очищает содержимое собственного UINibStorage (в т.ч. Пройдясь по всем классам и проверив все их селекторы, я получил единственный вариант — UINibStorage. Как же вышло, что UINib погиб, но при этом получил нотификацию?

объект уже практически погибает, и раз асинхронная операция в этот же момент пробует с ним работать, то это плохо кончится. Это неверный и страшный подход к использованию NSNotificationCenter. Одна из нередких ошибок, с которой доводилось сталкиваться — отмена либо отписывание от чего же-или в деаллоке. там выполняется много асинхронных операций. Неувязка, по всей видимости, появилась из-за того, что мы употребляли NSCache для кеширования нибов. К огорчению, грозная действительность такая, что не постоянно есть не плохое место, где можно было бы отписываться. по сущности асинхронно с memory warning в главном потоке. Т.о. Это очень поздний момент, т.к. Вообщем говоря, за время работы на проекте нам доводилось фиксить много багов, связанных с асинхронностью, т.к. В случае с UINib понятно, что такового комфортного места нет, потому трудно упрекнуть за это (быстрее тогда стоит упрекать инфраструктуру либо NSNotificationCenter). в фоновом потоке вызывается -[UINib dealloc], в котором тот отписывается от нотификаций, а в главном идет их обработка. При нехватке памяти NSCache очищает свое содержимое в фоновом потоке, т.е.

он не должен мыслить, что нельзя отправить release объекту в любом фоновом потоке из-за того, что этот release может быть крайним, а dealloc делает больше, чем ничего. Но и NSCache я тоже не могу именовать очевидно виноватым, т.к. Пожалуй, эта ситуация из тех, когда понятные и простительные решения дают нехороший итог. Ранее мне уже доводилось фиксить креш связанный с хранением NSCache в NSCache — так делать тоже не стоит. В качестве решения трудности я написал очевидный кеш для хранения нибов. Вообщем же, это не 1-ый креш с NSCache. habrahabr.ru