在UIScrollView和PageControl中卸载不再可见的视图和控制器的可能优化

3
我有一个页面控制代码,与苹果的示例相同。这里有一个子视图(controller.view),其中包含一个ImageView。现在的问题是内存管理。所有的都很好,但当我滚动5-10页时,RAM会被填满。 我尝试释放视图+控制器,但没有找到任何适当的位置/方法可以起作用。我想释放那些当前不可见的视图(除了当前、前一个和下一个视图)。
- (void)applicationDidFinishLaunching:(UIApplication *)application {
    NSMutableArray *controllers = [[NSMutableArray alloc] init];
    for (unsigned i = 0; i < kNumberOfPages; i++) {
        [controllers addObject:[NSNull null]];
    }
    self.viewControllers = controllers;
    [controllers release];

    scrollView.pagingEnabled = YES;
    scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * kNumberOfPages, scrollView.frame.size.height);
    scrollView.showsHorizontalScrollIndicator = NO;
    scrollView.showsVerticalScrollIndicator = NO;
    scrollView.scrollsToTop = NO;
    scrollView.delegate = self;

    pageControl.numberOfPages = kNumberOfPages;
    pageControl.currentPage = 0;

    [self loadScrollViewWithPage:0];
    [self loadScrollViewWithPage:1];
}
- (void)loadScrollViewWithPage:(int)page {
    if (page < 0) return;
    if (page >= kNumberOfPages) return;

    PageControlExampleViewControl *controller = [viewControllers objectAtIndex:page];
    if ((NSNull *)controller == [NSNull null]) {
        controller = [[PageControlExampleViewControl alloc] initWithPageNumber:page];
        [viewControllers replaceObjectAtIndex:page withObject:controller];
        [controller release];
    }

    if (nil == controller.view.superview) {
        CGRect frame = scrollView.frame;
        frame.origin.x = frame.size.width * page;
        frame.origin.y = 0;
        controller.view.frame = frame;
        [scrollView addSubview:controller.view];
    }
}

- (void)scrollViewDidScroll:(UIScrollView *)sender {
  if (pageControlUsed) {
        return;
    }
    CGFloat pageWidth = scrollView.frame.size.width;
    int page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
    pageControl.currentPage = page;

    [self loadScrollViewWithPage:page - 1];
    [self loadScrollViewWithPage:page];
    [self loadScrollViewWithPage:page + 1];

}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    pageControlUsed = NO;
}
- (IBAction)changePage:(id)sender {
    int page = pageControl.currentPage;
    [self loadScrollViewWithPage:page - 1];
    [self loadScrollViewWithPage:page];
    [self loadScrollViewWithPage:page + 1];
    CGRect frame = scrollView.frame;
    frame.origin.x = frame.size.width * page;
    frame.origin.y = 0;
    [scrollView scrollRectToVisible:frame animated:YES];
    pageControlUsed = YES;
}
4个回答

4
我尝试了 Deepmist 的代码几个小时。它完成了任务,但是我收到了内存警告,并且我的应用程序在滚动 25-30 页之后崩溃了(我在页面中使用了大图像)。在 Instruments 中,我注意到内存的使用非常大:即使不必要的视图被逐渐从 superview 中移除,并且相关的 viewControllers 被替换为 NSNulls,Instruments 显示每次滚动 4-5MB 的实际内存增加!
在网上搜索后,我发现这是一个常见问题。如果您也遇到此问题,应尝试以下检查:
1)在每个视图中,请确保使用 imageWithContentsOfFile 而不是 imageNamed。如文档所述,imageNamed 缓存图像并增加内存大小。
2)在 Deepmist 代码中,在以下代码之后:
[controller.view removeFromSuperview];

您还需要将视图设置为nil:

controller.view=nil;

这个技巧解决了内存消耗问题,现在仅加载三个视图(当前视图、当前视图前一项和当前视图后一项)以避免页面滚动时的闪烁。
希望这有所帮助!

1
在我苦思冥想之后,这对我有用。谢谢! - Tom Redman
1
把我的名字从Deepmist改成了Joe。很高兴你能解决这个问题。 - Joe

1

我没有测试过,但我想你可以编写一个方法来执行与加载相反的操作,如下所示:

- (void)unloadScrollViewWithPage:(int)page {
    if (page < 0) return;
    if (page >= kNumberOfPages) return;

    PageControlExampleViewControl *controller = [viewControllers objectAtIndex:page];

    if ((NSNull *)controller != [NSNull null]) {
        if (nil != controller.view.superview)
            [controller.view removeFromSuperview];

        [viewControllers replaceObjectAtIndex:page withObject:[NSNull null]];            
    }
}

然后在你的didScroll方法中添加一些代码,如下所示:

[self unloadScrollViewWithPage:page - 2];
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
[self unloadScrollViewWithPage:page + 2];

我尝试了你的帮助 :) 但是我现在面临同样的问题。我们正在同时从superview中删除视图并将其替换为null。因此,我遇到了EXC_BAD_ACCESS错误。“修改正在被完成的层”。有没有办法等待操作完成或者延迟执行? - Ruchit Patel
我不确定为什么您会收到那个错误。也许代码的另一部分在释放后仍在尝试使用视图?我将这段代码添加到了自己的项目中,其中包含相同的scrollview代码,似乎可以移除视图而不会出现任何错误。 - Joe

0
在loadScrollViewWithPage方法中,在if(nil == controller.view.superview)之前,循环遍历所有视图并删除除当前-1、当前和当前+1之外的所有视图,但仅在使用当前索引视图调用该方法时才执行。此外,不要忘记将那些视图控制器替换为NSNulls数组中的视图控制器。

0
尝试使用this VSScrollview,它像UITableview一样重复使用其视图。

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