Reputation: 659
My app continues to increase in 'live bytes' by about 600kb every time I scroll to a new image. I am loading my images lazily, meaning only 3 at a time, into a UIScrollView that pages horizontally. I have run Xcode instruments and I believe I have tracked the problem to this line:
newPageView = [[UIImageView alloc] initWithImage:[self.album objectAtIndex:page]];
Every page that gets loaded creates a new UIImageView instance, and for some reason, the space is held in memory. I am using ARC and XCode5. Since there is no way to 'dealloc' objects in ARC, what is the best way to free up this memory?
Here's a snapshot of my instruments showing the images that keep taking up the memory:
I have seen this question proposed in other areas online but could not find an answer that helped. I'm fairly new to coding but very willing to learn, any references would be greatly appreciated! thanks!
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.pageCount = self.album.count;
// Set up the array to hold the views for each page
self.pageViews = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < self.pageCount; ++i) {
[self.pageViews addObject:[NSNull null]];
}
NSInteger p = self.page;
CGFloat w = self.scrollView.bounds.size.width;
[self.scrollView setContentOffset:CGPointMake(p*w,0) animated:YES];
// Set up the content size of the scroll view
CGSize pagesScrollViewSize = self.scrollView.frame.size;
self.scrollView.contentSize = CGSizeMake(pagesScrollViewSize.width * self.pageCount, pagesScrollViewSize.height);
// Load the initial set of pages that are on screen
[self loadVisiblePages];
}
- (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));
// Keeps track of which image is showing, for passing to child view controller
self.currentImage = [self.album objectAtIndex:page];
self.title =[NSString stringWithFormat:@"%d of %d", [self.album indexOfObject:self.currentImage]+1, self.pageCount];
// Work out which pages we 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];
}
for (NSInteger i=firstPage; i<=lastPage; i++) {
[self loadPage:i];
}
for (NSInteger i=lastPage+1; i<self.pageCount; i++) {
[self purgePage:i];
}
}
- (void)loadPage:(NSInteger)page {
if (page < 0 || page >= self.pageCount) {
// If it's outside the range of what we have to display, then do nothing
return;
}
// Load an individual page, first seeing if we've already loaded it
UIView *pageView = [self.pageViews objectAtIndex:page];
// create an instance of imageView to be used as the newPageView
UIImageView *newPageView;
if ((NSNull*)pageView == [NSNull null]) {
CGRect frame = self.scrollView.bounds;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0.0f;
newPageView = [[UIImageView alloc] initWithImage:[self.album objectAtIndex:page]];
newPageView.contentMode = UIViewContentModeScaleAspectFit;
newPageView.frame = frame;
[self.scrollView addSubview:newPageView];
[self.pageViews replaceObjectAtIndex:page withObject:newPageView];
}
}
- (void)purgePage:(NSInteger)page {
if (page < 0 || page >= self.pageCount) {
// If it's outside the range of what we 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];
[self.pageViews replaceObjectAtIndex:page withObject:[NSNull null]];
[self.album replaceObjectAtIndex:page withObject:[NSNull null]];
}
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self loadVisiblePages];
}
Upvotes: 1
Views: 753
Reputation: 659
I've changed my code to resize the images as they are loaded as followed:
- (void)loadPage:(NSInteger)page {
if (page < 0 || page >= self.pageCount) {
// If it's outside the range of what we have to display, then do nothing
return;
}
// Load an individual page, first seeing if we've already loaded it
UIView *pageView = [self.pageViews objectAtIndex:page];
// create an instance of imageView to be used as the newPageView
UIImageView *newPageView;
if ((NSNull*)pageView == [NSNull null]) {
CGRect frame = self.scrollView.bounds;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0.0f;
self.resizeImage = [self.album objectAtIndex:page];
UIImage *theImage = [self resizeImage:self.resizeImage toWidth:320.0f andHeight:480.0f];
newPageView = [[UIImageView alloc] initWithImage:theImage];
newPageView.contentMode = UIViewContentModeScaleAspectFit;
newPageView.frame = frame;
[self.scrollView addSubview:newPageView];
[self.pageViews replaceObjectAtIndex:page withObject:newPageView];
}
}
- (UIImage *)resizeImage:(UIImage *)image toWidth:(float)width andHeight:(float)height {
CGSize newSize = CGSizeMake(width, height);
CGRect newRectangle = CGRectMake(0, 0, width, height);
UIGraphicsBeginImageContext(newSize);
[self.resizeImage drawInRect:newRectangle];
UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resizedImage;
}
This seems to be holding the memory at a stable place with no permanent increase in memory consumption. I am not sure if this is the best way to solve this problem so if anyone has any better ideas, please provide help.
Upvotes: 0
Reputation: 109
It's difficult to give an answer without see your code, but i suppose when you speak about "Lazy loading", you mean you only add your image to the scrollview when you need. But are you removing the images from the scrollview when they are not displayed?
I don't know which kind of display you want, but take a look on UICollectionView
. Collection view manage the memory itself and only display cells when needed and UICollectionViewLayout
allows you to customize the collection view.
Upvotes: 0
Reputation: 293
ARC tries to release memory automatically so you don't have to worry about doing so.
However, you can kind of 'mark' objects so they can be released by 'niling' them:
yourobject = nil;
this kind of tells the IOS device that the object is not being used and can be released. If you're loading images into a scrollview, that means as the user scrolls down, there is content still in memory that is not on-screen and therefore is not needed. You can get rid of these images and then load them again when the scrollview gets near a certain point.
You might find an answer in this question (which is very similar to yours) helpful: Memory pressure in app
Upvotes: 1