Releasing memory of multiple view controllers insi

2019-05-31 04:46发布

I have an app the loaded many view controllers in a scroll view depending on the number of objects the user has in a tableview. So when I flip between the tableview and the scroll view, the number of view controllers in the scroll view changes according to how many objects the user has in the tableview.

I use the code in Apple's PageControl sample code to build the scroll view with many view controllers inside it, after some modification of course.

- (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];
    }

}

The problem is the number of living view controllers (MainViewController here) keeps increasing when I flip between the table view and the scroll view (according to Instruments) even though I didn't add any new objects which causes memory problems of course.

I tried so many things in viewWillDisappear of the scroll view like:

- (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];
    }
 }

But it didn't work. Here is a recording of how the app works youtube.com/watch?v=5W8v_smZSog

And this is the viewWillAppear method of the scroll view:

- (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];
}

UPDATE: Video recording of Instruments http://www.youtube.com/watch?v=u1Rd2clvMQE&feature=youtube_gdata_player

And a screen shot showing the responsible caller: enter image description here

Thank you.

4条回答
看我几分像从前
2楼-- · 2019-05-31 05:10

It doesn't look like you are implementing Apple's View Controller Containment pratices. It would make memory management that much easier and safer.

Plus, hoping that it might save you a lot of future headaches, there is already an open source project that does what you are describing (implementing a self-managing scrollview of an arbritary number of view controllers).

You might want to take a look at it: RHHorizontalSwipe.

查看更多
爷的心禁止访问
3楼-- · 2019-05-31 05:12

Apple's PageControl sample code is 2 years old and you can consider it as deprecated because there is a new container view controller in iOS 5 that does all this: UIPageViewController.

You should really start using UIPageViewController, then you don't need that loadScrollViewWithPage method at all. It would be less code and more easy.

Take a look at the PhotoScroller sample code. It has been updated to take full advantage of UIPageViewController.

查看更多
何必那么认真
4楼-- · 2019-05-31 05:17

This is for you if you don't want to use UIPageViewController (read my other answer).

The sample project is designed for a constant number of pages (kNumberOfPages). The scrollview content size and the size of the view controller array depends on the number of pages. The sample code set this up in awakeFromNib, which is called only once.

So in order to make this dynamic you could recreate the whole ContentController when the number of pages changes. You just need to add a property for the number of pages.

The other option would be to reset the scrollview and view controller array when the number of pages changes.

I'm assuming you have defined a property for the events:

@property(nonatomic,retain) NSArray* eventsArray;

You could then add a setter method like this:

-(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];
    }
}

You call this method from the table view controller at the time when the user switches to the scroll view.

查看更多
冷血范
5楼-- · 2019-05-31 05:20

The concept of a UIScrollView containing multiple UIViewController views sounds sketchy at best, that design does not sound good at all.

That being said, one potential issue could be this line:

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

You would be better off with something like this:

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

Also, you should call [super viewWillDisappear:animated] in your viewWillDisappear method.

查看更多
登录 后发表回答