Сбой приложения iOS в режиме отладки, работа в режиме выпуска - PullRequest
0 голосов
/ 22 апреля 2019

Есть несколько вопросов, задающих прямо противоположное этому, и я не понимаю, как / почему работает мое приложение в режиме выпуска, но происходит сбой с ошибкой EXC_BAD_ACCESS в режиме отладки.

метод, который терпит крах, является рекурсивным, и чрезвычайно !!существенным;до тех пор, пока рекурсий не слишком много, он отлично работает как в режиме отладки (менее чем ~ 1000 на iPhone XS, без ограничений на симуляторе), так и в режиме выпуска (без ограничений?).

Я в недоумении относительно того, где начать выяснять, как отлаживать режим отладки, и мне интересно, есть ли какой-то рекурсивный мягкий предел, связанный из-за трассировки стека или какого-то другого неизвестного?Может быть, дело даже в кабеле, так как я могу без проблем работать в симуляторе?

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

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

Смотрите:https://gist.github.com/ThomasHaz/3aa89cc9b7bda6d98618449c9d6ea1e1

Ответы [ 2 ]

1 голос
/ 23 апреля 2019

Вам не хватает стековой памяти.

Рассмотрим эту очень простую рекурсивную функцию для сложения целых чисел от 1 до n:

func sum(to n: Int) -> Int {
    guard n > 0 else { return 0 }
    return n + sum(to: n - 1)
}

Вы обнаружите, что если попытаетесь, например, суммировать числа от 1 до100 000, приложение будет аварийно завершать работу как в выпускных, так и в отладочных сборках, но просто будет аварийно завершать работу при отладочных сборках.Я подозреваю, что в отладочных сборках в стек помещается только больше диагностической информации, из-за чего в стеке становится меньше места.В сборках релиза, описанных выше, указатель стека продвигается на 0x20 байт каждый рекурсивный вызов, тогда как отладочная сборка продвигается на 0x80 байт каждый раз.И если вы делаете что-то существенное в своей рекурсивной функции, эти приращения могут быть больше, и сбой может произойти с еще меньшим количеством рекурсивных вызовов.Но размер стека на моем устройстве (iPhone Xs Max) и на моем симуляторе (Thread.current.stackSize) составляет 524 288 байт, и это соответствует величине, на которую продвигается указатель стека, и максимальному числу рекурсивныхзвонки, которые я могу сделать.Если ваше устройство выходит из строя раньше, чем симулятор, возможно, ваше устройство имеет меньше оперативной памяти и поэтому выделило меньшую stackSize.

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

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

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


Я бы отослал вас к следующим видеороликам WWDC:


Стоит отметить, что глубоко рекурсивные процедуры не всегда должныпотреблять большой стек.Примечательно, что иногда мы можем использовать tail-recursion , где наш рекурсивный вызов - самый последний сделанный вызов.Например, мой фрагмент выше не использует хвостовой вызов, потому что он добавляет n к значению, возвращаемому рекурсивным вызовом.Но мы можем реорганизовать его для прохождения промежуточного итога, таким образом гарантируя, что рекурсивный вызов является истинным «хвостовым вызовом»:

func sum(to n: Int, previousTotal: Int = 0) -> Int {
    guard n > 0 else { return previousTotal }
    return sum(to: n - 1, previousTotal: previousTotal + n)
}

Сборки релиза достаточно умны, чтобы оптимизировать эту хвостовую рекурсию (через процесс, называемый«Оптимизация хвостового вызова», TCO, также известная как «устранение хвостового вызова»), уменьшая рост стека для рекурсивных вызовов.WWDC 2015 Профилирование по глубине , в то время как на другой теме, профилировщик времени, точно показывает, что происходит, когда он оптимизирует хвостовые вызовы.

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

0 голосов
/ 22 апреля 2019

EXEC_BAD_ACCESS обычно означает, что вы пытаетесь получить доступ к объекту, который не находится в памяти или, возможно, неправильно инициализирован.

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

Может быть множество причин, и вы не можете сказать много, не видя никакого кода.

Попробуйте включить NSZombieOjects - этоможет предоставить вам больше отладочной информации.См. Здесь Как включить NSZombie в XCode?

Если вы хотите знать, где и когда именно происходит ошибка, вы можете проверить утечки памяти с помощью инструментов.Это может быть полезно http://www.raywenderlich.com/2696/instruments-tutorial-for-ios-how-to-debug-memory-leaks

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...