Reputation: 21
I'm developing a simple iPad app (iOS 5.1.1 and XCode 4.3.3 on Lion) that horizontally scrolls through images. It uses lazy loading but unfortunately it is crashing due to low memory about 3/4 of the way through the images. I've used the XCode analyzer and Instruments to find and fix any leaks and it still crashes. The crash log is below:
Incident Identifier: 91B65E89-4CCE-4CB5-975F-365D23D802E5
CrashReporter Key: 659f640ad55a7dfbaf0305305e0b8f0fb36a6c41
Hardware Model: iPad1,1
OS Version: iPhone OS 5.1.1 (9B206)
Kernel Version: Darwin Kernel Version 11.0.0: Sun Apr 8 21:51:26 PDT 2012; root:xnu-1878.11.10~1/RELEASE_ARM_S5L8930X
Date: 2012-06-29 14:51:58 -0700
Time since snapshot: 49 ms
Free pages: 610
Active pages: 0
Inactive pages: 694
Throttled pages: 43266
Purgeable pages: 0
Wired pages: 18518
Largest process: GBTL
Processes
Name UUID Count resident pages
GBTL <d26b776787493174ae14f454a11b746d> 31295 (jettisoned) (active)
debugserver <2408bf4540f63c55b656243d522df7b2> 134
installd <0f9e14173c503a8089f7f8cd0329a1a0> 403
atc <1e5f2a595709376b97f7f0fa29368ef1> 605 (jettisoned)
notification_pro <373a488638c436b48ef0801b212593c4> 96
notification_pro <373a488638c436b48ef0801b212593c4> 99
notification_pro <373a488638c436b48ef0801b212593c4> 96
afcd <c3cc9d594b523fd1902fb69add11c25d> 133
MobileMail <eed7992f4c1d3050a7fb5d04f1534030> 1004 (jettisoned)
ptpd <62bc5573db7a352ab68409e87dc9abb9> 356 (jettisoned)
mediaserverd <f03b746f09293fd39a6079c135e7ed00> 404 (jettisoned)
iapd <0a747292a113307abb17216274976be5> 285 (jettisoned)
DTMobileIS <1383b8ed7d373b59a0839e89ab947089> 899
notification_pro <373a488638c436b48ef0801b212593c4> 99
dataaccessd <473ff40f3bfd3f71b5e3b4335b2011ee> 735 (jettisoned)
springboardservi <b74f5f58317031e9aef7e95744c816ca> 283
syslog_relay <b07876a121a432d39d89daf531e8f2bd> 62
notification_pro <373a488638c436b48ef0801b212593c4> 98
syslog_relay <b07876a121a432d39d89daf531e8f2bd> 66
notification_pro <373a488638c436b48ef0801b212593c4> 98
filecoordination <0f34c714b10d35318f0d445f1d953386> 98
geod <976e1080853233b1856b13cbd81fdcc3> 127
absinthed.K48 <04b40efabeac392ba63cba7256b86bb0> 74
notifyd <f6a9aa19d33c3962aad3a77571017958> 181
aosnotifyd <8cf4ef51f0c635dc920be1d4ad81b322> 369
BTServer <31e82dfa7ccd364fb8fcc650f6194790> 186
CommCenterClassi <041d4491826e3c6b911943eddf6aaac9> 274
SpringBoard <c74dc89dec1c3392b3f7ac891869644a> 4878 (active)
aggregated <a12fa71e6997362c83e0c23d8b4eb5b7> 356
apsd <e7a29f2034083510b5439c0fb5de7ef1> 630
configd <ee72b01d85c33a24b3548fa40fbe519c> 304
fairplayd.K48 <47deae2d0fb23b6594b0128353e8b48d> 115
fseventsd <914b28fa8f8a362fabcc47294380c81c> 175
imagent <9c3a4f75d1303349a53fc6555ea25cd7> 361
locationd <cf31b0cddd2d3791a2bfcd6033c99045> 833
mDNSResponder <86ccd4633a6c3c7caf44f51ce4aca96d> 188
mediaremoted <327f00bfc10b3820b4a74b9666b0c758> 201
lockdownd <b06de06b9f6939d3afc607b968841ab9> 216
powerd <133b7397f5603cf8bef209d4172d6c39> 133
syslogd <7153b590e0353520a19b74a14654eaaa> 97
wifid <3001cd0a61fe357d95f170247e5458f5> 258
UserEventAgent <dc32e6824fd33bf189b266102751314f> 362
launchd <5fec01c378a030a8bd23062689abb07f> 123
**End**
And here is the relevant code:
#import "GBTLViewController.h"
@implementation GBTLViewController
@synthesize scrollView;
@synthesize pageControl;
@synthesize pageImages;
@synthesize pageViews;
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self loadVisiblePages];
if (!pageControlBeingUsed) {
// Update the page when more than 50% of the previous/next page is visible
CGFloat pageWidth = self.scrollView.frame.size.width;
int page = floor((self.scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
self.pageControl.currentPage = page;
}
}
- (IBAction)changePage {
// update the scroll view to the appropriate page
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * self.pageControl.currentPage;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
[self.scrollView scrollRectToVisible:frame animated:YES];
pageControlBeingUsed = YES;
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
pageControlBeingUsed = NO;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
pageControlBeingUsed = NO;
}
- (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.pageImages.count; i++) {
[self purgePage:i];
}
}
- (void)loadPage:(NSInteger)page {
if (page < 0 || page >= self.pageImages.count) {
// If it's outside the range of what you have to display, then do nothing
return;
}
UIView *pageView = [self.pageViews objectAtIndex:page];
if ((NSNull*)pageView == [NSNull null]) {
CGRect frame = self.scrollView.bounds;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0.0f;
UIImageView *newPageView = [[UIImageView alloc] initWithImage:[self.pageImages objectAtIndex:page]];
newPageView.contentMode = UIViewContentModeScaleAspectFit;
newPageView.frame = frame;
[self.scrollView addSubview:newPageView];
[newPageView release];
[self.pageViews replaceObjectAtIndex:page withObject:newPageView];
}
}
- (void)purgePage:(NSInteger)page {
if (page < 0 || page >= self.pageImages.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];
[self.pageViews replaceObjectAtIndex:page withObject:[NSNull null]];
}
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
pageControlBeingUsed = NO;
scrollView.delegate = self;
[self.scrollView setBackgroundColor:[UIColor blackColor]];
scrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
scrollView.clipsToBounds = NO;
scrollView.scrollEnabled = YES;
scrollView.pagingEnabled = NO;
self.pageImages = [NSArray arrayWithObjects:
[UIImage imageNamed:@"480X_cover.jpg"],
[UIImage imageNamed:@"GBTL1.jpg"],
[UIImage imageNamed:@"GBTL2.jpg"],
[UIImage imageNamed:@"GBTL3.jpg"],
[UIImage imageNamed:@"GBTL4.jpg"],
[UIImage imageNamed:@"GBTL5.jpg"],
[UIImage imageNamed:@"GBTL6.jpg"],
[UIImage imageNamed:@"GBTL7.jpg"],
[UIImage imageNamed:@"GBTL8.jpg"],
[UIImage imageNamed:@"GBTL9.jpg"],
[UIImage imageNamed:@"GBTL10.jpg"],
[UIImage imageNamed:@"GBTL11.jpg"],
[UIImage imageNamed:@"GBTL12.jpg"],
[UIImage imageNamed:@"GBTL13.jpg"],
[UIImage imageNamed:@"GBTL14.jpg"],
[UIImage imageNamed:@"GBTL15.jpg"],
[UIImage imageNamed:@"GBTL16.jpg"],
[UIImage imageNamed:@"GBTL17.jpg"],
[UIImage imageNamed:@"GBTL18.jpg"],
[UIImage imageNamed:@"GBTL19.jpg"],
[UIImage imageNamed:@"GBTL20.jpg"],
nil];
NSInteger pageCount = self.pageImages.count;
self.pageControl.currentPage = 0;
self.pageControl.numberOfPages = pageCount;
self.pageViews = [[[NSMutableArray alloc] init] autorelease];
for (NSInteger i = 0; i < pageCount; ++i) {
[self.pageViews addObject:[NSNull null]];
}
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
CGSize pagesScrollViewSize = self.scrollView.frame.size;
self.scrollView.contentSize = CGSizeMake(pagesScrollViewSize.width * self.pageImages.count, pagesScrollViewSize.height);
[self loadVisiblePages];
}
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
NSInteger pageCount = self.pageImages.count;
for (NSInteger i=0; i<pageCount; i++) {
[self purgePage:i];
}
CGSize pagesScrollViewSize = self.scrollView.frame.size;
self.scrollView.contentSize = CGSizeMake(pagesScrollViewSize.width * pageCount, pagesScrollViewSize.height);
[self loadVisiblePages];
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
self.scrollView = nil;
self.pageControl = nil;
}
- (void)dealloc {
[scrollView release];
[pageControl release];
[super dealloc];
}
@end
If anyone can figure out where the memory issue is happening and how to fix it, I would be very grateful. Thanks!
Upvotes: 2
Views: 1641
Reputation: 4490
One problem is with the following two lines from your loadPage and purgePage methods:
if ((NSNull*)pageView == [NSNull null]) {
if ((NSNull*)pageView != [NSNull null]) {
The right-hand side ([NSNull null]
) creates a new object and you are comparing pageView
with the address of that object. Even if pageView
is another object created with [NSNull null]
, its address will most likely not match the new object you are creating in the if
conditional. It would be much better to use nil
to designate pages you have yet to load. You could do this easily by making the pageViews
field an NSMutableDictionary
(with NSUInteger
as the key type). Short of that, you could also replace those checks with checks of the following sort:
if ([pageView isTypeOfClass:[NSNull class]]) {
if (![pageView isTypeOfClass:[NSNull class]]) {
Upvotes: 1
Reputation: 3317
That is quite a large array of images you have there. Perhaps store the NSString names of the images in an array and only have the one or two images on screen being active.
Upvotes: 1
Reputation: 9039
Don't know what size images these are but there are a number of strategies for handling this type of situation:
1) Do you really need to load the images into an NSArray? It seems like based on your file naming scheme you could construct some logic to pull it out of your bundle as needed and then discard as soon as off screen.
2) Sometimes it is advantageous to do this type of thing in an @autorelease block. See here: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsautoreleasepool_Class/Reference/Reference.html
3) You might want to look at this example App downscaling of large images for lower resolution screen: http://developer.apple.com/library/ios/#samplecode/LargeImageDownsizing/Introduction/Intro.html#//apple_ref/doc/uid/DTS40011173
Upvotes: 2