user1492253
user1492253

Reputation: 21

App crashing, low memory

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

Answers (3)

Turix
Turix

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

WolfLink
WolfLink

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

Cliff Ribaudo
Cliff Ribaudo

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

Related Questions