【问题标题】:Releasing memory of multiple view controllers inside a scrollview在滚动视图中释放多个视图控制器的内存
【发布时间】:2012-10-18 14:35:30
【问题描述】:

我有一个应用程序在滚动视图中加载了许多视图控制器,具体取决于用户在表格视图中拥有的对象数量。因此,当我在 tableview 和滚动视图之间切换时,滚动视图中的视图控制器数量会根据用户在 tableview 中拥有的对象数量而变化。

我使用 Apple 的 PageControl 示例代码中的代码来构建滚动视图,其中包含许多视图控制器,当然经过一些修改。

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

   // replace the placeholder if necessary
   MainViewController *countdownController = [viewControllers objectAtIndex:page];
   if ((NSNull *)countdownController == [NSNull null]) 
   {

      id occasion = [eventsArray objectAtIndex:page];

      countdownController = [[MainViewController alloc] initWithPageNumber:page];
      [countdownController setOccasion:occasion];

      [viewControllers replaceObjectAtIndex:page withObject:countdownController];


      [countdownController release];

    }

    // add the controller's view to the scroll view
    if (nil == countdownController.view.superview) 
    {
      CGRect frame = scrollView.frame;
      frame.origin.x = frame.size.width * page;
      frame.origin.y = 0;
      countdownController.view.frame = frame;
      [scrollView addSubview:countdownController.view];
    }

}

问题是当我在表格视图和滚动视图(根据 Instruments)之间切换时,活动视图控制器(此处为 MainViewController)的数量不断增加,即使我没有添加任何会导致内存问题的新对象.

我在滚动视图的 viewWillDisappear 中尝试了很多东西,例如:

- (void) viewWillDisappear:(BOOL)animated
{


    //test unloading all views
    //Remove all subviews
    [[scrollView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];

    //[[scrollView subviews] makeObjectsPerformSelector:@selector(release)];


    //[viewControllers removeAllObjects];
    for (unsigned m = 0; m < [viewControllers count]; m++)
    {
     //[[viewControllers objectAtIndex:m] makeObjectsPerformSelector:@selector(release)];

      [viewControllers removeObjectAtIndex:m];
    }
 }

但它没有用。 这是该应用程序如何工作的记录 youtube.com/watch?v=5W8v_smZSog

这是滚动视图的 viewWillAppear 方法:

- (void)viewWillAppear:(BOOL)animated
{

    eventsArray = [[NSMutableArray alloc] init];

    kNumberOfPages = [self.dataModel occasionCount];

    //update the eventsArray from the dataModel
    //Fill in the events Array with occasions form the data model
    for (unsigned r = 0; r < kNumberOfPages; r++)
    {
        Occasion* occasion = [self.dataModel occasionAtIndex:r];
        [eventsArray insertObject:occasion atIndex:r];
    }

     // view controllers are created lazily
     // in the meantime, load the array with placeholders which will be replaced on   demand
    NSMutableArray *controllers = [[NSMutableArray alloc] init];
    for (unsigned i = 0; i < kNumberOfPages; i++)
    {
        [controllers addObject:[NSNull null]];
     }

    self.viewControllers = controllers;
    [controllers release];

    // a page is the width of the scroll view
    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 = currentPage;

    [self loadScrollViewWithPage:0];
    [self loadScrollViewWithPage:1];
}

更新:仪器视频录制http://www.youtube.com/watch?v=u1Rd2clvMQE&feature=youtube_gdata_player

以及显示负责呼叫者的屏幕截图:

谢谢。

【问题讨论】:

  • 我认为将视图控制器视图放在表格视图中是不寻常的——使用普通的UITableViewCells 怎么样?
  • 我没有将视图控制器放在表格视图中,而是放在滚动视图中。这些视图控制器的数量取决于表格视图中的数据。
  • 同样的概念也适用.. 为什么不加载一组视图并将它们放在滚动视图中呢?你设置它,这样你就有一个拥有滚动视图和子视图的视图控制器。
  • 这就是我正在做的,它工作正常。问题是我释放视图控制器后内存没有释放。我通过检查实时视图控制器的数量和不断增加的内存在仪器中验证了这一点。
  • 代码是多少?我在 2 天内将一个大项目移至 ARC。

标签: ios memory uiviewcontroller uitableview scrollview


【解决方案1】:

包含多个 UIViewController 视图的 UIScrollView 的概念充其量听起来很粗略,这种设计听起来一点也不好。

话虽如此,一个潜在的问题可能是这一行:

if ((NSNull *)countdownController == [NSNull null]) 

这样的话你会更好:

if (!countdownController || [countdownController isKindOfClass:[NSNull class]])

此外,您应该在您的 viewWillDisappear 方法中调用 [super viewWillDisappear:animated]

【讨论】:

    【解决方案2】:

    如果您不想使用UIPageViewController,这是给您的(请阅读我的其他答案)。

    示例项目设计用于固定页数 (kNumberOfPages)。滚动视图内容大小和视图控制器数组的大小取决于页数。示例代码在仅调用一次的 awakeFromNib 中进行了设置。

    因此,为了使这种动态化,您可以在页面数量发生变化时重新创建整个 ContentController。您只需要为页数添加一个属性。

    另一种选择是在页面数量发生变化时重置滚动视图和视图控制器数组。

    我假设您已经为事件定义了一个属性:

    @property(nonatomic,retain) NSArray* eventsArray;
    

    然后你可以像这样添加一个 setter 方法:

    -(void)setEventsArray:(NSArray *)eventsArray
    {
        if (eventsArray != _eventsArray) {
            [_eventsArray release];
            _eventsArray = [eventsArray retain];
            NSUInteger eventCount = [eventsArray count];
            //reset scrollview contentSize
            scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * eventCount, scrollView.frame.size.height);
    
            // reset content offset to zero
            scrollView.contentOffset = CGPointZero;
    
            //remove all subviews
            [[scrollView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
    
            pageControl.numberOfPages = eventCount;
    
            // reset viewcontroller array
            NSMutableArray *controllers = [[NSMutableArray alloc] init];
            for (unsigned i = 0; i < eventCount; i++)
            {
                [controllers addObject:[NSNull null]];
            }
            self.viewControllers = controllers;
            [controllers release];
    
            [self loadScrollViewWithPage:0];
            [self loadScrollViewWithPage:1];
        }
    }
    

    您在用户切换到滚动视图时从表视图控制器调用此方法。

    【讨论】:

    • 这很有趣,但我已经修改了 Apple PageControl 代码以允许任意数量的页面,并且我已经有一个单独的数据模型来获取页面数量。所以我的代码有效,但我遇到了内存问题,尽管当滚动视图消失时我正在释放视图控制器。而且我看不出你的代码是如何解决这个内存问题的。
    • 您清空了滚动视图和视图控制器数组,但仍有活的视图控制器实例?然后必须有一些对 MainViewControllers 的更强大的引用。检查是否在 MainViewController 中调用了 dealloc。
    • 是的。即使我释放它们,也不会在任何 MainViewController 实例中调用 dealloc,但我真的不知道为什么。
    • 哦,一个小提示:我没有清空视图控制器数组。我该如何正确地做到这一点?我尝试了一些使应用程序崩溃的方法。所以我对正确的做法持开放态度。
    • 你试过我的解决方案了吗?我正在使用 NSNull 对象重置数组。
    【解决方案3】:

    Apple 的 PageControl 示例代码已有 2 年历史,您可以将其视为已弃用,因为 iOS 5 中有一个新的容器视图控制器可以执行所有这些操作:UIPageViewController

    你应该真正开始使用UIPageViewController,然后你根本不需要那个 loadScrollViewWithPage 方法。这将是更少的代码,更容易。

    查看PhotoScroller 示例代码。它已更新以充分利用UIPageViewController

    【讨论】:

    • 很好的答案,但是 UIPageViewController 的滚动过渡样式仅适用于 iOS 6。如果他想针对 iOS 5,那不是一个选择。
    【解决方案4】:

    您似乎没有实施 Apple 的 View Controller Containment 做法。这将使内存管理变得更加容易和安全。

    另外,希望它可以为您节省很多未来的麻烦,已经有一个开源项目可以完成您所描述的工作(实现任意数量的视图控制器的自我管理滚动视图)。

    您可能想看看它:RHHorizontalSwipe

    【讨论】:

    • 我阅读了视图控制器包含实践页面。我将滚动视图控制器用作容器视图控制器,它负责推送新的子视图控制器并释放它们。我不知道我上面的代码具体是什么导致无法正确释放子视图控制器的内存。
    • 那你在哪里打电话给removeFromParentViewController
    • 在上面的第二个代码块中,在我的容器滚动视图的 viewWillDisappear 下的问题中,我正在这样做:[[scrollView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
    • 但仅此而已......使用该行,您只是从视图层次结构中删除视图,而不是视图控制器本身,它仍然分配在内存中。如果您已经在使用视图控制器包含,那么当您向其中一个孩子发送 removeFromParentViewController 消息时,它将在不久之后有效地释放。请尝试了解removeFromParentViewControllerremoveFromSuperview 有很大不同的原因(除了一个是UIViewController 方法,另一个是UIView 方法)。 Apple 的文档对此非常清楚。
    • 这很有趣,也很有意义。但是当我尝试removeFromParentViewController 而不是removeFromSuperview 时,应用程序崩溃了! viewWillDisAppear 是不是叫错地方了?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-14
    • 1970-01-01
    相关资源
    最近更新 更多