Я просто провел целую неделю, разыскивая и бьющий утечки памяти по голове, и я прибыл в другой конец той недели, немного ошеломленной. Должен быть лучший способ сделать это, все, что я могу думать, и таким образом, я полагал, что пришло время спросить об этом довольно тяжелом предмете.
Это сообщение оказалось довольно огромным. Гарантированы извинения за это, хотя я думаю в этом случае, объясняя детали максимально полностью. Явно так, потому что это дает Вам целое изображение всех вещей, которые я сделал для нахождения этого педераста, который был много. Одна только эта ошибка взяла меня примерно три 10 + дни часа для разыскивания...
Когда я ищу утечки
Когда я ищу утечки, я склонен делать это в фазах, где я возрастаю "глубже" в проблему, если это не разрешимо в более ранней фазе. Эти фазы начинаются с Утечек, говоря мне существует проблема.
В данном случае (который является примером; ошибка решена; я не прошу ответы на решение этой ошибки, я прошу способы улучшить процесс, в котором я нахожу ошибку), я нахожу утечку (два, даже) в многопоточном приложении, которое довольно крупно, особенно включая приблизительно 3 внешних библиотеки, которыми я пользуюсь в нем (разархивируйте функцию и http сервер). Поэтому давайте посмотрим процесс, где я фиксирую эту утечку.
Фаза 1: Утечки говорят мне, что существует утечка
(источник: enrogue.com)
Ну, это интересно. Так как мое приложение является многопоточным, моя первая мысль - то, что я забыл помещать NSAutoreleasePool
в где-нибудь, но после регистрации во всех правильных местах, дело обстоит не так. Я смотрю на отслеживание стека.
Фаза 2: отслеживание стека
(источник: enrogue.com)
Оба из GeneralBlock-160
утечки имеют идентичные отслеживания стека (который нечетен, так как мне сгруппировали его "идентичные следы", но так или иначе), которые запускаются в thread_assign_default
и конец в malloc
под _NSAPDataCreate
. Промежуточный, нет абсолютно ничего, что коррелирует к моему приложению. Ни один из тех вызовов не являются "моими". Таким образом, я делаю некоторый поиск с помощью Google вокруг для выяснения то, для чего они могли бы использоваться.
Сначала у нас есть много методов, которые, очевидно, имеют отношение к обратному вызову потока, такому как вызовы потока POSIX, входящие в вызовы NSThread.
В № 8-6 в этом (инвертированном) отслеживании стека мы имеем +[NSThread exit]
сопровождаемый pthread_exit
и _pthread_exit
который интересен, но по моему опыту я не могу действительно сказать, показательно ли из некоторого конкретного случая или если это просто, "как дела идут".
После этого у нас есть названный метод очистки потока _pthread_tsd_cleanup
- независимо от того, что "tsd" стоит, поскольку я не уверен, но независимо, я иду дальше.
В # 4-#3 мы имеем:
CA::Transaction::release_thread(void*)
CAPushAutoreleasePool
Интересный. Мы имеем Core Animation
здесь. Это, я изучил очень твердый путь, средства, что я, вероятно, делаю UIKit
вызовы от фонового потока, который я не должен. Большой вопрос состоит в том где, и как. В то время как может быть легко сказать, что "Вы не должны звонить UIKit
от Вас olde фоновый поток", не столь легко знать то, что точно составляет как a UIKit
звонить. Как Вы будете видеть в этом случае, это совсем не очевидно.
Затем № 2-1 оказывается слишком низким уровнем, чтобы иметь любое реальное применение. Я думаю.
У меня все еще нет подсказки, где даже начать искать эту утечку памяти. Таким образом, я делаю единственную вещь, я могу думать.
Фаза 3: return
в изобилии
Предложите, чтобы у нас было дерево вызова, которое выглядит примерно так:
App start
|
Some init
| \
A init B init - Other case - Fourth case
\ / \
Some case Third case
|
Fifth case
...
Грубая схема жизненного цикла приложения, этого. Короче говоря, у нас есть много путей, в зависимости от которых может взять приложение что бы ни случилось, и каждый из этих путей включает набор кода, называемого в различных местах. Таким образом, я вытаскиваю ножницы и начинаю прерывать. Я запускаю близко к "Приложению, запускаются" первоначально и медленно спускают строку к перекрестку, где я только позволяю один путь.
Таким образом, я имею
// ...
[fooClass doSomethingAwesome:withThisCoolThing];
// ...
И я делаю
// ...
return;
[fooClass doSomethingAwesome:withThisCoolThing];
// ...
И затем установите приложение на устройстве, закройте его, alt-tab к Инструментам, поразите CMDR, молоток на приложении как обезьяна, ищите утечки, и после возможно, 10 "циклов", если нет ничего, я прихожу к заключению, что утечка далее снижается на код. Возможно в fooClass
doSomethingAwesome:
или ниже вызова к fooClass
.
Таким образом, я перемещаю тот возврат один шаг ниже вызова к fooClass
и тест снова. Если утечка не кажется теперь, большой, fooClass
невинно.
Существует несколько проблем с этим методом.
UIImage *a;
(который, очевидно, не протекает отдельно),// 17 14.48.25: 3 leaks @ RSx10
который на английском языке имел в виду "17-го июля, 14:48.25: 3 утечки произошли, когда я неоднократно выбирал объект, 10 раз" опрыснутый всюду по целому приложению. Грязный, но по крайней мере это позволило мне видеть ясно, где я протестировал вещи и каковы результаты были.Этот метод в конечном счете удалил меня к самой нижней части класса, который обработал миниатюры. Класс имел два метода, тот, который инициализировал вещи и затем сделал a [NSThread detachThreadWithSeparator:]
звоните в отдельный метод, который обработал действительные образы и поместил их в отдельные представления после уменьшения масштаба их к правильному размеру.
Это был вид подобных это:
// no leaks if I return here
[NSThread detachNewThreadSelector:@selector(loadThumbnails) toTarget:self withObject:nil];
// leaks appear if I return here
Но если я вошел -loadThumbnails
и пониженный через него, утечки исчезли бы и появились бы очень случайным способом. При одном обширном выполнении у меня были бы утечки и если я спустил оператор возврата ниже, например. UIImage *small, *bloated;
У меня было бы появление утечек. Короче говоря, это было очень ошибочно.
Еще после некоторого тестирования я понял, что утечки имели бы тенденцию появляться чаще, если бы я перезагрузил вещи, более быстрые в то время как в приложении. После многих часов боли я понял, что, если эта внешняя резьба не закончила выполняться, прежде чем я загрузил другую сессию (таким образом создающий второй класс миниатюры и отбрасывающий этого), утечка появится.
Это - хорошая подсказка. Таким образом, я добавил a BOOL
названный worldExists
который был установлен на NO
как только новая сессия инициировалась и затем начала опрыскивать -loadThumbnails
for
цикл с
if (worldExists) [action]
if (worldExists) [action 2]
// ...
и также удостоверился, что вышел из цикла, как только я узнал это !worldExists
. Но утечка осталась.
И return
метод показывал утечки в очень ошибочных местах. Случайным образом это появилось.
Таким образом, я пытался добавить это в самом верху -loadThumbnails
:
for (int i = 0; i < 50 && worldExists; i++) {
[NSThread sleepForTimeInterval:0.1f];
}
return;
И хотите верьте, хотите нет, но утечки на самом деле появился, если я загрузил новую сессию в течение 5 секунд.
Наконец, я вставил точку останова -dealloc
для класса миниатюры. Отслеживание стека для этого было похоже на это:
#0 -[Thumbs dealloc] (self=0x162ec0, _cmd=0x32299664) at /Users/me/Documents/myapp/Classes/Thumbs.m:28
#1 0x32c0571a in -[NSObject release] ()
#2 0x32b824d0 in __NSFinalizeThreadData ()
#3 0x30c3e598 in _pthread_tsd_cleanup ()
#4 0x30c3e2b2 in _pthread_exit ()
#5 0x30c3e216 in pthread_exit ()
#6 0x32b15ffe in +[NSThread exit] ()
#7 0x32b81d16 in __NSThread__main__ ()
#8 0x30c8f78c in _pthread_start ()
#9 0x30c85078 in thread_start ()
Хорошо... это не выглядит слишком плохо. Если я ожидаю до -loadThumbnails
метод закончен, трассировка выглядит по-другому хотя:
#0 -[Thumbs dealloc] (self=0x194880, _cmd=0x32299664) at /Users/me/Documents/myapp/Classes/Thumbs.m:26
#1 0x32c0571a in -[NSObject release] ()
#2 0x00009556 in -[WorldLoader dealloc] (self=0x192ba0, _cmd=0x32299664) at /Users/me/Documents/myapp/Classes/WorldLoader.m:33
#3 0x32c0571a in -[NSObject release] ()
#4 0x000045b2 in -[WorldViewController setupWorldWithPath:] (self=0x11e9d0, _cmd=0x3fee0, path=0x4cb84) at /Users/me/Documents/myapp/Classes/WorldViewController.m:98
#5 0x32c29ffa in -[NSObject performSelector:withObject:] ()
#6 0x32b81ece in __NSThreadPerformPerform ()
#7 0x32c23c14 in CFRunLoopRunSpecific ()
#8 0x32c234e0 in CFRunLoopRunInMode ()
#9 0x30d620da in GSEventRunModal ()
#10 0x30d62186 in GSEventRun ()
#11 0x314d54c8 in -[UIApplication _run] ()
#12 0x314d39f2 in UIApplicationMain ()
#13 0x00002fd2 in main (argc=1, argv=0x2ffff5dc) at /Users/me/Documents/myapp/main.m:14
Очень отличающийся, на самом деле. На данном этапе я был все еще невежествен, хотите верьте, хотите нет, но я наконец выяснил то, что продолжалось.
Проблема следующая: когда я делаю [NSThread detachNewThreadSelector:]
в загрузчике миниатюры, NSThread
сохраняет объект, пока поток не заканчивается. В случае, где загрузка миниатюры не заканчивается, прежде чем я загружу другую сессию, весь мой сохраняет на загрузчике миниатюры, выпущены, но так как поток все еще работает, NSThread
поддерживает его.
Как только поток возвращается из -loadThumbnails
, NSThread
выпуски это, это совершает нападки 0, сохраняют, и идет прямо в -dealloc
... в то время как все еще в фоновом режиме распараллеливают.
И когда я затем звоню [super dealloc]
, UIView
покорно попытки удалить себя из его суперпредставления, которое является a UIKit
обратитесь к фоновому потоку. Следовательно, утечка происходит.
Решение, которое я предложил для решения этого, состояло в том, чтобы перенести загрузчик в два других метода. Я переименовал его к -_loadThumbnails
и затем сделал следующее:
[self retain]; // <-- added this before the detaching
[NSThread detachNewThreadSelector:@selector(loadThumbnails) toTarget:self withObject:nil];
// added these two new methods
- (void)doneLoadingThumbnails
{
[self release];
}
-(void)loadThumbnails
{
[self _loadThumbnails];
[self performSelectorOnMainThread:@selector(doneLoadingThumbnails) withObject:nil waitUntilDone:NO];
}
Все, что сказало (и я сказал много - извините о том), большой вопрос: как Вы понимаете эти чудные вещи, не проходя все вышеупомянутое?
Какое обоснование я пропускал в вышеупомянутом процессе? В какой точке Вы понимали, где проблема была? Каковы были избыточные шаги в моем методе? Я могу пропустить фазу 3 (return
в изобилии), так или иначе, или сокращенный это, или делают это более эффективным?
Я знаю, что этот вопрос, ну, в общем, неопределенен и огромен, но это целое понятие неопределенно и огромно. Я не прошу, чтобы Вы учили меня, как найти утечки (я могу сделать это... это просто очень, очень болезненно), я спрашиваю, что люди склонны делать для сокращения времени процесса. Выяснение у людей, "как Вы находите утечки?" невозможно, потому что существует столько различных видов. Но один тип, с которым я склонен иметь проблемы, является тем, который похож на вышеупомянутое без вызовов в Вашем реальном приложении.
Какой процесс Вы используете, чтобы более эффективно разыскать его?
Какие аргументы я пропустил в приведенном выше process?
Совместное использование объектов UIView между несколькими потоками должно было вызвать очень громкие сигналы тревоги в вашей голове, почти сразу после написания кода.