1

I'm having a problem in my iPhone application, when I run it on my iPod it takes more than 10 minutes before it crashes and closes. So I looked for profile statistics and seems to be high Live Bytes as it is in the image below:

Live Bytes

Any one can help me decreasing this Live Bytes to avoid the crash ?

EDIT: I'm using three for loops when calling viewDidLoad, and the three loops get images from references, so I think it lets the app crash ? So what is an alternative for doing these three for loops without causing crash ?

EDIT 2: This is my view did load, each loop gets different images from different folders, and all of them are necessary

EDIT 3: For folder a , it has 100 images, half of them are thumbnails for each image, for folder b, 30 images, and finally folder c 90 images, half of them are thumbnails for each image, and all of folders are in references so it is not getting any images from internet...

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    NSInteger x = 6;
    NSString *resourcePath = [[NSBundle mainBundle] resourcePath];

    NSString *aPath = [resourcePath stringByAppendingPathComponent:@"a"];
    NSArray *aDirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:aPath error:nil];
    for (NSString *tString in aDirContents) {
        if ([tString hasSuffix:@".jpg"]) {
            UIButton *item = [UIButton buttonWithType:UIButtonTypeCustom];
            [item setFrame:CGRectMake(x, 5, 60, 60)];
            x += 63;
            NSString *result = [tString substringToIndex:[tString length] - 4];
            [item setTitle:result forState:UIControlStateNormal];
            [item setTitleColor:[UIColor clearColor] forState:UIControlStateNormal];
            [item setBackgroundImage:[UIImage imageNamed:tString] forState:UIControlStateNormal];
            [item addTarget:self action:@selector(changeFont:) forControlEvents:UIControlEventTouchUpInside];

            [aSlider addSubview:item];
        }
    }
    aSlider.frame = CGRectMake(aSlider.frame.origin.x, aSlider.frame.origin.y, x, 69);
    [aSliderContainer setContentSize:aSlider.frame.size];

    x = 6;
    NSString *bPath = [resourcePath stringByAppendingPathComponent:@"b"];
    NSArray *bDirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:bPath error:nil];
    for (NSString *tString in bDirContents) {
        if ([tString hasSuffix:@".jpg"]) {
            UIButton *item = [UIButton buttonWithType:UIButtonTypeCustom];
            [item setFrame:CGRectMake(x, 5, 60, 60)];
            x += 63;
            NSString *result = [tString substringToIndex:[tString length] - 4];
            [item setTitle:result forState:UIControlStateNormal];
            [item setTitleColor:[UIColor clearColor] forState:UIControlStateNormal];
            [item setBackgroundImage:[UIImage imageNamed:tString] forState:UIControlStateNormal];
            [item addTarget:self action:@selector(changeFont:) forControlEvents:UIControlEventTouchUpInside];

            [bSlider addSubview:item];
        }
    }
    bSlider.frame = CGRectMake(bSlider.frame.origin.x, bSlider.frame.origin.y, x, 69);
    [bSliderContainer setContentSize:bSlider.frame.size];

    // c Slider
    x = 6;
    NSString *cPath = [resourcePath stringByAppendingPathComponent:@"c"];
    NSArray *cDirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:cPath error:nil];
    for (NSString *tString in cDirContents) {
        if ([tString hasSuffix:@".jpg"]) {
            UIButton *item = [UIButton buttonWithType:UIButtonTypeCustom];
            [item setFrame:CGRectMake(x, 5, 60, 60)];
            x += 63;
            NSString *result = [tString substringToIndex:[tString length] - 4];
            [item setTitle:result forState:UIControlStateNormal];
            [item setTitleColor:[UIColor clearColor] forState:UIControlStateNormal];
            [item setBackgroundImage:[UIImage imageNamed:tString] forState:UIControlStateNormal];
            [item addTarget:self action:@selector(changeFont:) forControlEvents:UIControlEventTouchUpInside];

            [cSlider addSubview:item];
        }
    }
    cSlider.frame = CGRectMake(cSlider.frame.origin.x, cSlider.frame.origin.y, x, 69);
    [cSliderContainer setContentSize:cSlider.frame.size];
}
Sayed
  • 1,022
  • 3
  • 12
  • 27
  • My app's "Live Bytes" is almost 120 MB at times and still doesn't crash (on iPad2 or greater). I don't think live bytes is your problem. – Putz1103 Jun 25 '13 at 16:48
  • 5.7 mb doesn't seem outrageous, so first confirm that memory consumption is causing the crash. But if you want to reduce memory consumption, you have to (a) make sure you don't have any leaks or retain cycles; and (b) look at what objects your app is creating and minimize that if possible. Avoid, for example, loading arrays of images or silly things like that, employ lazy loading, respond to `didReceiveMemoryWarning`, etc. I don't know how else we can possibly help you with so little information about your app, the details of the crash, the memory usage, etc. – Rob Jun 25 '13 at 16:56
  • First run Analyzer. That will help find any storage leaks and will also help identify some other bugs. – Hot Licks Jun 25 '13 at 17:53
  • @Putz1103 can you see the edit please – Sayed Jun 25 '13 at 17:57
  • @Rob yeah, any way to avoid crashes from loops ? – Sayed Jun 25 '13 at 17:59
  • @phplover I'd discourage you from loading images in a loop in `viewDidLoad`. See my answer below. I'd suggest you update your question with your `viewDidLoad` code and details about the crash/exception, or else this question will inevitably be closed as not being constructive. – Rob Jun 25 '13 at 18:23
  • 1
    @Rob I've updated with my viewDidLoad – Sayed Jun 25 '13 at 18:25

2 Answers2

4

Given the revised question, in which you're loading images from persistent storage into (presumably) a series of scroll views, a couple of thoughts:

  1. How many images are there?

    If there are only 5 or 6 each, then pursuing an infinite scrolling class is a futile exercise. If there are 50+, then infinite scrolling is very important pattern to employ. (Google "UIImageView infinite scroll".) This is a variation on the "lazy loading" discussion in my original answer, but is geared towards the unique situation of scroll views.

  2. How big are the images?

    Are they already 60x60 (or possibly, for retina, 120x120)? If they're much bigger than that, you may want to resize them to be of an optimal size for your UI. You could either do this on the original assets in your app's bundle, or you could programmatically resize them.

  3. When your app crashes, what exception/error log do you see?

    Can you please confirm the precisely the details from crash log and/or error message, exception codes, etc.

    • While it may appear to be cryptic, this information is invaluable in terms of diagnosing the problem. We really want to make sure this viewDidLoad code is actually the problem, and if so, what precisely the error is.

    • If you haven't already, I might turn on an exception breakpoint (go ahead and do it for "all" exceptions). Sometimes it can help highlight the offending line of code (if the problem isn't simply running out of memory or being killed by the watch dog process).

Please go ahead and update your question with the relevant details.


Original answer (prior to seeing viewDidLoad code):

I'd encourage you to pursue "lazy loading" of images, load them only when they're needed by the UI, and releasing them (or removing all of your strong references to them) when they're no longer needed (i.e. the image in question scrolls out off the screen). I would be very wary of loading images in a loop in viewDidLoad. It wastes precious memory and, if you're doing this synchronously, your app might even be killed by the watch dog process because your app always should be responsive in the main queue. You can tell if the watch dog kills your app if you get an exception code of 0x8badf00d (geek humor: the watchdog is reporting "ate bad food"; see TN2151 for a description of a few exception codes).

The simplest solution for this is to employ the UIImageView category in either SDWebImage or AFNetworking. If you're using a UITableView or UICollectionView, this is incredibly simple. If you're using a UIScrollView, then it takes a little more work (unless you employ a third-party "infinite scrolling" class).

Images take a lot of memory, so only hold what you need for the UI at any given moment and don't "prefetch" images unnecessarily. I'd encourage you to google the phrase "UIImage lazy loading" and you'll get tons of hits.


If you want to do an infinite scroller, define a model object for each InfiniteScrollerButton:

@interface InfiniteScrollerButton : NSObject

@property (nonatomic, copy, readonly) NSString *filename;  // what is the full path of the image
@property (nonatomic, weak, readonly) UIButton *button;    // the UIButton; nil if no button yet created for this icon
@property (nonatomic, readonly)       CGRect    frame;     // the frame that the icon does (or would) occupy
@property (nonatomic, readonly)       NSInteger tag;       // the tag number for the button  

- (id)initWithFilename:(NSString *)filename index:(NSInteger)index;
- (void)addButtonToView:(UIView *)view target:(id)target action:(SEL)action;
- (void)removeButton;

@end

And the implementation of that might look like:

@interface InfiniteScrollerButton ()

@property (nonatomic, copy) NSString *filename;
@property (nonatomic, weak) UIButton *button;
@property (nonatomic)       CGRect    frame;
@property (nonatomic)       NSInteger tag;

@end

@implementation InfiniteScrollerButton

- (id)initWithFilename:(NSString *)filename index:(NSInteger)index
{
    self = [super init];
    if (self) {
        _filename = [filename copy];
        _frame = CGRectMake(6 + index * 63, 5, 60, 60);
        _tag = index;
    }
    return self;
}

- (void)addButtonToView:(UIView *)view target:(id)target action:(SEL)action
{
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    self.button = button;

    // on old devices, even retrieving an image from persistent storage can affect the smoothness
    // of the UI, so let's do that asynchronously

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        UIImage *image = [UIImage imageWithContentsOfFile:self.filename];

        dispatch_async(dispatch_get_main_queue(), ^{
            button.frame = self.frame;
            button.tag = self.tag;
            NSString *tString = [self.filename lastPathComponent];
            NSString *result = [tString substringToIndex:[tString length] - 4];
            [button setTitle:result forState:UIControlStateNormal];
            [button setTitleColor:[UIColor clearColor] forState:UIControlStateNormal];
            [button setBackgroundImage:image forState:UIControlStateNormal];
            [button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
            [view addSubview:button];
        });
    });
}

- (void)removeButton
{
    if (self.button)
    {
        [self.button removeFromSuperview];
        self.button = nil;
    }
}

@end

Then your view controller might look like:

@interface ViewController () <UIScrollViewDelegate>

@property (nonatomic, strong) NSMutableArray *icons;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self loadModel];
    [self scrollViewDidScroll:self.scrollView]; // call this once, manually, so the initial load of visible images takes place
}

// load the model backing our scrollview

- (void)loadModel
{
    self.icons = [NSMutableArray array];

    NSString *resourcePath = [[NSBundle mainBundle] resourcePath];

    NSString *aPath = [resourcePath stringByAppendingPathComponent:@"a"];
    NSArray *aDirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:aPath error:nil];
    [aDirContents enumerateObjectsUsingBlock:^(NSString *tString, NSUInteger idx, BOOL *stop) {
        InfiniteScrollerButton *icon = [[InfiniteScrollerButton alloc] initWithFilename:[aPath stringByAppendingPathComponent:tString] index:idx];
        [self.icons addObject:icon];
    }];

    InfiniteScrollerButton *lastObject = [self.icons lastObject];
    self.scrollView.contentSize = CGSizeMake(lastObject.frame.origin.x + lastObject.frame.size.width + 6, 69);
}

- (void)changeFont:(id)sender
{
    NSLog(@"%s", __FUNCTION__);
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    static NSInteger kMargin = 100;

    // get the CGRect for the visible portion of the scroll view,
    // adding a bit of a margin so it, effectively, will also load
    // the next UIButton, too

    CGRect contentRect = CGRectMake(scrollView.contentOffset.x - kMargin,
                                    scrollView.contentOffset.y,
                                    scrollView.bounds.size.width + kMargin * 2,
                                    scrollView.bounds.size.height);

    // iterate through all of the icons

    for (InfiniteScrollerButton *icon in self.icons)
    {
        if (CGRectIntersectsRect(contentRect, icon.frame))    // if the icon should be visible ...
        {
            if (icon.button == nil)                           //  ... but it's not, then add it
                [icon addButtonToView:scrollView
                               target:self
                               action:@selector(changeFont:)];
        }
        else                                                  // if the icon is no longer visible ...
        {
            if (icon.button != nil)                           //   ... but it exists, then remove it
                [icon removeButton];
        }
    }
}

@end

Just make sure to specify your view controller as the delegate for the scroll view, and you're off to the races. Obviously, this only has a single scroll view, but hopefully you get the idea. Your viewDidLoad should not create the UIButton controls, but rather just populate a model array backing your UI. Then your scroll view will call the delegate method, scrollViewDidScroll, which will do the loading and unloading of the images as they're needed. If you had thousands and thousands of images, I might suggest other optimizations, but hopefully this illustrates the concept.

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thank you for being active answering my question :) , you can see the third edit, I've just updated the question. – Sayed Jun 25 '13 at 20:55
  • @phplover You've got enough there that I'd pursue a infinite scroller pattern. It's not so many that I would have thought that you'd be crashing, though. For diagnostic purposes, temporarily change the number of images to some small number (e.g. 5-10) for a, b, and c and see if it now works. If it does, then memory is possibly the issue and you can use infinite scroller to solve it. If it still crashes, then there is another problem lurking out there. There's no way for us to diagnose this remotely. While you should do infinite scroller, I'm not at all confident that's the problem. We'll see. – Rob Jun 25 '13 at 21:18
  • yes when I have a small number of images in the slider, it works just fine on the iPod. – Sayed Jun 26 '13 at 08:11
  • @phplover That confirms it, then: An infinite scroller pattern will tremendously reduce your memory high-water mark. – Rob Jun 26 '13 at 12:33
0

Are you developing using ARC or allocating the memory yourself. Try to release objects as they become obsolete. The memory allocations is not that high. Have you locked into the crash logs? Have a look here on how to use these logs...

Community
  • 1
  • 1
David Karlsson
  • 9,396
  • 9
  • 58
  • 103