Подтвердить что ты не робот

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];
}
4b9b3361

Ответ 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 и имеют слишком много страниц, слишком быстрое перелистывание, безусловно, приведет к краху приложения.

Одним из решений является настройка уровня и кэширование результата рендеринга. Повторно отрисуйте содержимое только тогда, когда это необходимо. Но кеш может увеличить использование памяти.