UIPageViewController在低内存情况下快速翻转时崩溃

19
由于 Xcode UIPageViewController 模板缓存所有页面数据,导致出现了一些内存问题。为此,我将页面更改为动态加载方式,这样当我的应用程序接收到低内存警告时,它会释放未显示页面的内存。但是,如果用户通过快速点击屏幕边缘来快速翻页,它仍然会崩溃。我猜测这是因为在调用didReceiveMemoryWarning时无法快速释放内存所导致的。如果用户翻页的速度比较慢,那么就能够正常工作。我限制了用户翻页的速度,但问题仍然存在。我希望能够每次翻页时释放内存,而不必等待低内存警告。我正在使用ARC。有没有办法做到这一点?或者还有什么其他方法可以预防这种情况?谢谢。
(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
先发布你的代码,这将是最有帮助的。 - futureelite7
请发布您的 - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController 方法的代码。 - Rok Jarc
5个回答

5
我认为你的假设是正确的,因为我也遇到了类似的情况:当你翻转到下一页时,为了使动画效果更好,新页面会在旧页面被释放之前进行分配,而旧页面需要一些时间才能被释放。所以,当你翻得足够快时,对象的分配速度比它们的释放速度更快,最终(实际上很快),由于内存使用量过高,你的应用程序将被杀死。如果你在Instruments中跟踪内存的分配/释放,就会发现翻页时的释放延迟非常明显。
在我看来,你有三种方法可以解决这个问题:
1. 实现一个“轻量级”的 viewDidLoad 方法(实际上是整个初始化/初始显示序列):在某些应用中,这是有意义的,例如加载低分辨率图像而不是将要显示的高分辨率图像,或者稍微延迟分配页面所需的其他资源(数据库访问、声音等);
2. 使用页面池,比如一个由三个页面(或5个,这取决于你的应用程序)组成的数组,以便你可以“重复使用”,这样你的应用程序的内存配置文件保持稳定,避免出现波动;
3. 仔细审查你分配和释放内存的方式;在这方面,你经常会读到autorelease对释放/释放机制添加了一些“惯性”,这很容易理解:如果你有一个autorelease对象,它将只在你循环遍历主循环时才被其释放池释放(这对于主释放池是正确的);所以,如果你有一长串方法,当翻页时调用它们,这将使释放/释放延迟更长。
在内存使用量优化方面,没有神奇的方法,这需要非常详细和艰苦的工作,但根据我的经验,如果你检查代码并应用这三个准则,就能够减少应用程序的内存配置文件。尤其是,在Instruments中检查内存分配的峰值,并试图理解它们的相关性,这是非常有用的。

2
一些额外的有用信息:我被一位苹果工程师告知,从iOS6开始,“viewWillAppear”在页面首次变为可见时被调用(当开始翻页时),而“viewDidAppear”在页面完成翻转时被调用。我还没有尝试过,只是想提一下。利用这个,您可以锁定翻页的能力,直到前一个页面完成(通过忽略手势/触摸)。 - Marty

2
这是我做出的一个额外更改,也许有人会觉得有用:
基本上,我只允许在前一页完成后才开始新的翻页。
我使用苹果默认的PageViewController项目作为模板,因此我将使用该项目中定义的术语。
每当通过viewControllerAtIndex:请求页面VC时,我会在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;
}

最后,在UIPageViewController委托方法pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:的末尾,我设置了_modelController.shouldDenyPageTurn = NO

同时,我还必须在任何预加载的结尾设置_modelController.shouldDenyPageTurn = NO,以便可以立即允许页面翻转。


0

这可能是由于渲染引起的。当翻页速度过快时,重新绘制“页面”所使用的内存和CPU将迅速增加。如果您在UIPageViewController中使用的视图基于CALayer并且有太多页面,则翻页速度过快肯定会导致应用程序崩溃。

一种解决方案是自定义层并缓存渲染结果。仅在必要时重新渲染内容。但缓存可能会增加内存使用量。


似乎我有这种问题。你能给更多细节吗?你所说的缓存渲染是什么意思?我正在尝试构建一个儿童应用程序,类似于动物声音。每个动物详细信息中都有关键帧动画和声音。每个动物详细信息视图控制器都在UIPageViewController中。向右滑动时,当我点击一个动物时,CALayer关键帧动画和播放声音会被触发。UiPageviewController已经增加了内存使用量,动物动画的CALayer动画是最后一击。我该怎么办? - Add080bbA

0

目前在iOS5中有一个bug,会导致滚动视图泄露一小部分内存。

你尝试过使用Instruments工具对你的应用进行分析,检查内存分配和内存泄漏了吗?

你可以在模拟器中模拟低内存警告(硬件 -> 模拟低内存警告)。或者你也可以通过代码来实现(但是请记得在调试后将其移除,因为这会导致你的应用被拒绝!)

[[UIApplication sharedApplication] performSelector:@selector(_performMemoryWarning)];

如果您正在使用strongretain属性,请在使用完后将它们设置为nil,ARC会在幕后释放它们所指向的内存。

如果您正在创建大量临时对象(不是属性或未分配的对象),请插入自动释放池:

@autoreleasepool {

}

最后,请展示一些代码,这样我们可以更好地帮助您。

0

由于您没有发布任何代码,很难猜测您的问题所在。

  1. 为了强制卸载视图,您可以重写出现在UIPageViewController中的那些视图控制器类的viewDidDisappear:方法。

    代码如下:

    - (void)viewDidDisappear:(BOOL)animated {
        [self didReceiveMemoryWarning];
    }
    

    如果您还覆盖了didReceiveMemoryWarning,请不要忘记从中调用[super didReceiveMemoryWarning];

  2. 关于UIPageViewControllerDataSource方法的工作原理可能会有一些混淆-您可能会有一些“混乱的电线”。请查看接受的答案here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接