iosuiscrollviewuiimagelazy-loadingimagenamed

Lazy loading images in scrollview seems to increase memory


I am lazy loading images in a scrollview. However as I scroll the content memory allocation increase and it is rather sluggish. Here is my code;

- (void)scrollViewSetUp
{

    self.scrollview.delegate = self;
    self.automaticallyAdjustsScrollViewInsets = NO;
    NSInteger pageCount = self.saleImages.count;


    self.pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(0.0,0.0,self.view.frame.size.width,40.0)];
    [self.pageControl setNumberOfPages:pageCount];
    [self.pageControl setCurrentPage:0];
    self.pageControl.pageIndicatorTintColor = [UIColor lightGrayColor];
    self.pageControl.currentPageIndicatorTintColor = [UIColor whiteColor];
    [self.pageControl setBackgroundColor:[UIColor clearColor]];


       //Next, you set up the array that holds the UIImageView instances. At first, no pages have been lazily loaded and so you just fill it with the right amount of NSNull objects that are needed – one for each page.
       //You’re using [NSNull null] because it’s a lightweight singleton object that can be added to an array to signify a placeholder.
       //Later on, you’ll use the fact that there is an NSNull in there to know if that page is loaded or not.
        self.pageViews = [[NSMutableArray alloc] init];
        for (NSInteger i = 0; i < pageCount; i++) {
            [self.pageViews addObject:[NSNull null]];
        }

    }

Here is my loadPage: method;

-(void)loadPage:(NSInteger)page{
    if (page < 0 || (page >= self.saleImages.count)) {
        //if its outside the range of what you have to display, then do nothing
        return;
    }


    //First, you check if you've already loaded the view. If you haven't, then the object in the pageViews array will be an NSNull ( remember, [NSNull null] is a special singleton which is why == works).
    UIView * pageView = [self.pageViews objectAtIndex:page];

    if ((NSNull*) pageView == [NSNull null]) {

        CGRect frame = self.view.bounds;//set to views bounds instead of scrollviews bounds because it doesnt work very well with autolayout
        frame.origin.x = (frame.size.width+kScrollViewPadding) * page;
        frame.origin.y = 0.0f;

        UIImageView * newPageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[self.saleImages objectAtIndex:page]]];
        newPageView.contentMode = UIViewContentModeScaleToFill;
        newPageView.frame = CGRectMake(frame.origin.x,frame.origin.y,frame.size.width, frame.size.height);

        [self.scrollview addSubview:newPageView];

    //Finally, you replace the NSNull in pageViews array with the view you've kust created, so that if this page was asked to load again you would now not go into the if statement and instead do nothing since the view for the page has already been created.

        [self.pageViews replaceObjectAtIndex:page withObject:newPageView];
    }


}

Here is my purgePage and loadVisiblePages;

-(void)purgePage:(NSInteger)page{
    if (page <0 || page >= self.saleImages.count) {
        //if it's outside the range of what you have to display, then do nothing
        return;
    }

    //Remove a page from the scroll view and reset the container array
    UIView * pageView = [self.pageViews objectAtIndex:page];
    if ((NSNull *)pageView != [NSNull null]) {
        [pageView removeFromSuperview];
        pageView = nil;
        [self.pageViews replaceObjectAtIndex:page withObject:[NSNull null]];

    }

}



-(void)loadVisiblePages{
    //first determine which page is currently visible
    CGFloat pageWidth = self.scrollview.frame.size.width;
    NSInteger page = (NSInteger)floor((self.scrollview.contentOffset.x * 2.0f + pageWidth)/(pageWidth * 2.0f));

    //update the page control
    self.pageControl.currentPage = page;

    //Work out which pages you want to load
    NSInteger firstPage = page -1;
    NSInteger lastPage = page+1;

    //purge anything before the first page
    for (NSInteger i = 0; i<firstPage; i++) {
        [self purgePage:i];
    }

    //load pages in our range
    for (NSInteger i = firstPage; i<=lastPage; i++) {
        [self loadPage:i];
    }

    //purge anything after the last page
    for (NSInteger i = lastPage +1; i<self.saleImages.count; i++) {
        [self purgePage:i];
    }

}

I personally feel there is something wrong with my purgePage method. Here I remove the pageView from the superview and set it to nil. I hope someone can help because Im tearing my hair out.

Edit 1 after doing a Generation Analysis in instruments it seems that the UIImage is increasing.The culprit class is loadPage and loadVisiblePages.

enter image description here

Edit 2 @Wain pointed out that imageNamed: does some caching.


Solution

  • You are currently using imageNamed: and it caches the images that are loaded to improve performance. This isn't always ideal and you may need to avoid using that convenience method.

    To avoid using imageNamed:, you need to get the path of the image (probably from the bundle) and then use initWithContentsOfFile:.

    Note that removing all of the caching may be too extreme and you may want to create your own cache (like with a dictionary) where you have control of how many images are cached at any one time. Also, depending on how you're displaying the images you may want to add some different size images into your app so that you can load the most appropriate size for each situation.

    Note also that you can load images in the background if you need to, and then push the image to the main thread to update the UI.