UIPageViewController сбой при слишком быстрой перелистывании во время низкой памяти
У меня возникли проблемы с памятью из-за шаблона Xcode для UIPageViewController, кэширующего все данные страницы, поэтому я изменил его, чтобы динамически загружать страницы, поэтому теперь, когда мое приложение получает предупреждение о низкой памяти, оно освобождает память для страницы, не отображающей, но если пользователь быстро пролистывает страницы, нажав на край экрана, он все равно падает. Я предполагаю, что это связано с тем, что он не может быстро освободить память, когда вызывается callReceiveMemoryWarning. Если пользователь медленно переворачивается, он работает нормально. Я ограничил скорость, с которой пользователь может листать страницы, но это все еще происходит. Я хочу, чтобы освободить память каждый раз, когда страница была повернута, и не нужно ждать предупреждения с низкой памятью. Я использую ARC. Есть ли способ сделать это? Или что еще я могу сделать, чтобы это предотвратить? Спасибо.
EDIT:
(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(SinglePageViewControllerSuperclass *)viewController];
if ((index == 0) || (index == NSNotFound)) {
return nil;
}
index--;
return [self viewControllerAtIndex:index];
}
(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(SinglePageViewControllerSuperclass *)viewController];
if (index == NSNotFound || index == MAX_PAGE_INDEX) {
return nil;
}
return [self viewControllerAtIndex:++index];
}
Ответы
Ответ 1
Я думаю, что ваша гипотеза правильная, так как я также испытал подобное поведение: когда вы переходите на следующую страницу, а также ради того, чтобы анимировать вещи красиво, новая страница выделяется до того, как старая будет освобождена, и требуется несколько раз для старого, который будет освобожден. Таким образом, когда вы быстро переключаетесь, объекты распределяются быстрее, чем они освобождаются, и в конечном итоге (на самом деле, довольно скоро) ваше приложение убивается из-за использования памяти. Задержка освобождения при перелистывании страниц становится довольно очевидной, если вы следуете за распределением/освобождением памяти в Instruments
.
У вас есть три подхода к этому: IMO:
-
реализовать "легкий" viewDidLoad
метод (фактически, всю инициализацию/начальную последовательность отображения): в каком-то приложении имеет смысл, например, загружать изображение с низким разрешением вместо изображения с высоким разрешением, которое будет отображаться; или, немного задерживая выделение дополнительных ресурсов, необходимых вашей странице (доступ к db, звук и т.д.);
-
используйте пул страниц, скажем, массив из трех страниц (или 5, это зависит от вашего приложения), что вы продолжаете "повторно использовать", чтобы профиль памяти вашего приложения оставался стабильным и избегал всплесков; /p >
-
внимательно просмотрите способ выделения и освобождения памяти; в этом смысле вы часто читаете, что автореклама добавляет некоторую "инерцию" к механизму освобождения/освобождения, и это довольно легко понять: если у вас есть автореализованный объект, он будет выпущен пулом релизов только тогда, когда вы будете проходить через основной цикл (это верно для основного пула релизов); поэтому, если у вас есть длинная последовательность методов, которые вызывается при переворачивании страницы, это приведет к тому, что release/dealloc произойдет позже.
Нет никакой волшебной пули, когда дело доходит до оптимизации использования памяти, это довольно подробная и тяжелая работа, но IME вы сможете уменьшить профиль памяти вашего приложения, если вы просмотрите свой код и примените эти 3 рекомендации. Тем более, что проверка пиков распределения памяти в Инструментах и попытка понять, к чему они относятся, чрезвычайно эффективны.
Ответ 2
Вот дополнительное изменение, которое я сделал, которое может найти полезное:
В принципе, я разрешаю запуск новой страницы, только если предыдущий закончен.
Я использую проект pageViewController по умолчанию в качестве шаблона, поэтому буду использовать термины, определенные в этом проекте.
Всякий раз, когда запрашивается страница VC через viewControllerAtIndex:, я устанавливаю логическое значение в ModelController с именем "shouldDenyVC
" на YES
.
В моем EbookViewController, который является делегатом UIPageViewController, я фиксирую распознаватели жестов и назначаю EbookViewController в качестве своего делегата:
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
for (UIGestureRecognizer *gr in self.view.gestureRecognizers) {
gr.delegate = self;
}
Затем я могу отклонить поворот страницы, отклонив распознаватели жестов:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch: (UITouch *)touch
{
if (_modelController.shouldDenyPageTurn == YES) {
return FALSE;
}
return TRUE;
}
И, наконец, я установил _modelController.shouldDenyPageTurn = NO
в конце метода делегата UIPageViewController pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
Мне также пришлось установить _modelController.shouldDenyPageTurn = NO
в конце любой предварительной загрузки, чтобы разрешить поворот страницы с места в карьер.
Ответ 3
В iOS5 есть ошибка в данный момент, которая вызывает прокрутку, чтобы просачивать небольшой объем памяти.
Вы пробовали профилировать свое приложение в инструментах, проверяющих распределение и утечку памяти?
Вы можете имитировать предупреждение о низкой памяти либо в симуляторе (аппаратное обеспечение → имитировать предупреждение о низкой памяти). Или вы можете сделать это с помощью кода (просто не забудьте удалить после отладки, потому что это отклонит ваше приложение!)
[[UIApplication sharedApplication] performSelector:@selector(_performMemoryWarning)];
Если вы используете свойства strong
или retain
, затем установите их в nil
после того, как вы закончите с ними, и ARC освободит память, на которую они ссылаются, за кулисами.
Если вы создаете много временных объектов (объектов, которые не являются свойствами или не alloc'd), тогда вставьте пул автозапуска:
@autoreleasepool {
}
И, наконец, покажите код, и мы можем вам помочь.
Ответ 4
Поскольку вы не публиковали какой-либо код, трудно угадать, где именно лежит ваша проблема.
-
Чтобы принудительно выгрузить представление, вы можете переопределить метод viewDidDisappear:
тех классов viewcontroller, которые появляются в UIPageViewController
.
Код будет выглядеть так:
- (void)viewDidDisappear:(BOOL)animated {
[self didReceiveMemoryWarning];
}
Если у вас также есть makeReceiveMemoryWarning overriden, не забудьте вызвать [super didReceiveMemoryWarning];
из него.
-
Также может быть некоторая путаница в том, как работают методы UIPageViewControllerDataSource
- у вас могут быть некоторые "смешанные провода". Проверьте принятый ответ здесь.
Ответ 5
Это может быть вызвано рендерингом. Когда флиппер слишком быстро, память и процессор, используемые при перерисовке "страницы", будут быстро расти. Если представления, используемые в UIPageViewController, основаны на CALayer и имеют слишком много страниц, слишком быстрое перелистывание, безусловно, приведет к краху приложения.
Одним из решений является настройка уровня и кэширование результата рендеринга. Повторно отрисуйте содержимое только тогда, когда это необходимо. Но кеш может увеличить использование памяти.