1

I am presenting a two columned scrollview of images similar to Pinterest by using PSCollectionView https://github.com/ptshih/PSCollectionView and loading the images asynchronously using https://github.com/rs/SDWebImage

On iPhone 5 it is running continuously with no crashes, but on iPhone 4 I run into memory warnings and while scrolling down continually the app eventually crashes.

SDWebImage claims they release cache upon receiving a memory warning and the PSCollectionView manages the # of views by dequeuing reusable views, so I'm not sure what exactly is triggering the problems in memory.

I've ran memory profiling but I am not seeing anything that is glaringly obvious--the most memory intensive operation is coming from SDWebImage loading the images asynchronously. It does seem like the objectForKey method of getting data from my PFObjects (from parse.com) also takes a good chunk of memory.

I've attached below in particular the code snippet that is setting the contents of each PSCollectionView Cell.

Please let me know if you see any specific problems with the code that may lead to memory problems & also let me know if you have suggestions on how to better diagnose this memory problem.

- (UIView *)collectionView:(PSCollectionView *)collectionView cellForRowAtIndex:(NSInteger)index withRect:(CGRect) recttouse {


    FPCollectionViewCell *thefpcell = (FPCollectionViewCell *)
    [collectionView dequeueReusableViewForClass:[FPCollectionViewCell class]];

    thefpcell.frame = recttouse;

    if (thefpcell == nil) {


        thefpcell = [[FPCollectionViewCell alloc] initWithFrame:recttouse];

        //NSLog(@"creating this cell: %i", index);

        UILabel *cellText = [[UILabel alloc] initWithFrame:CGRectMake(2,0,143,20)];

        thefpcell.cellText = cellText;
        thefpcell.cellText.textAlignment = NSTextAlignmentCenter;


        UIImageView *cellImage = [[UIImageView alloc] initWithFrame:CGRectMake(0,20,recttouse.size.width, recttouse.size.height-20)];

        thefpcell.cellImage = cellImage;

         thefpcell.backgroundColor = [UIColor whiteColor];
        thefpcell.cellText.font = [UIFont fontWithName:@"HelveticaNeue-LightItalic" size:9];

        thefpcell.cellText.backgroundColor = [UIColor clearColor];


        [thefpcell addSubview:cellText];
        [thefpcell addSubview:cellImage];

        }

    //@Brian note--consider moving more of this to other blocks to try and improve performance
    [[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{

            PFObject *thisobj = [self.contentObjectsArray objectAtIndex:index];

         thefpcell.cellText.frame = CGRectMake(2,0,143,20);


        thefpcell.cellImage.frame = CGRectMake(0,20,recttouse.size.width, recttouse.size.height-20);


        thefpcell.cellText.text = [thisobj objectForKey:@"Caption"];

        NSString *imglink = [thisobj objectForKey:@"imgLink"];
        NSString *imgurl;
        if(imglink.length<2)
        {
            PFFile *mydata = [thisobj objectForKey:@"imageFile"];
            imgurl = mydata.url;

        }
        else
        {
            imgurl =imglink;
        }

              UIImage *cellplaceholder = [UIImage imageWithContentsOfFile:@"placeholder.png"];
        [thefpcell.cellImage setImageWithURL:[NSURL URLWithString:imgurl] placeholderImage:cellplaceholder];


        thefpcell.layer.cornerRadius = 9.0;
        thefpcell.layer.masksToBounds = YES;

        }]];


    //NSLog(@"got it: %i", index);

    return thefpcell;
}
Kerni
  • 15,241
  • 5
  • 36
  • 57

2 Answers2

1

Hmm. There's nothing obvious here.

SDWebImage registers for UIApplicationDidReceiveMemoryWarningNotification (which is important because in iOS7, NSCache does not respond to memory pressure like it used to). You can confirm this by placing a breakpoint in clearMemory in SDImageCache, and run the app, simulating/reproducing memory warning and confirm, and confirm that this is getting called. But I think you'll find it is. I can't speak to parse.com's handling of the fact that NSCache is no longer automatically purged like it used to be.

You should do some heapshots/generations in Instruments as illustrated in WWDC 2012 video iOS App Performance: Memory. So run the the app, get it to a point of quiescence, simulate memory warning, mark heapshot/generation, use the app a bit, simulate memory warning again, and mark heapshot/generation again. Then look at the the objects allocated and still live between the two generations and you'll be on your way towards identifying what's not getting released on you. You can probably identify whether there's something that parse.com isn't cleaning up, or whether it's in your code or in SDWebImage.


A couple of unrelated observations:

  1. This is going to sound like curious counsel given that you're trying to reduce memory impact, but cellplaceholder is probably the one situation where you really do want to use imageNamed rather than imageWithContentsOfFile. Assuming this placeholder will be needed a lot, it will (a) be faster; and (b) might actually negligibly reduce your high water mark if the placeholder is temporarily used a lot (rather than having a bunch of short-live instances of the same placeholder).

  2. It's extremely unlikely, but if you're going to be updating the cell asynchronously yourself, you really should be re-retrieving the collection view cell to make sure this cell hasn't scrolled off the screen (call [collectionView cellForItemAtIndexPath:indexPath], not to be confused with the similarly named UICollectionViewDataSource method) and make sure that's not nil).

    Frankly I'm not sure why you're dispatching this back to the main queue, anyway. SDWebImage will retrieve the images asynchronously already, so you don't generally have to do any asynchronous processing yourself, and if you really needed some asynchronous processing, you'd send that to some background queue (not back to the main queue).

    Regardless this construct:

    [[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{
        // ...
    }]];
    

    could be simplified to

    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        // ...
    }];
    
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thanks very much for your answer. I will take your advice on the placeholder image for sure. I will run the heap generation and add a profile to this so there's more information to go on. Is there anything you think I should do with autorelease pools and the UIImages loaded by SDWebImage? – BrianAllenToronto Nov 25 '13 at 01:41
  • I ran the profiler and for the time range when I open the scrollview, 7.18 MB or 59.2% of bytes used are used by "CAKeyframeanimation CA_preparerendervalue. 30% of bytes used are for SDImageCache DiskImageForKey. This time running profiling I got to a higher memory cap than I ever did before (approximately 8 megs live bytes) but it never received a memory warning. I will test further but the only change was resizing images before loading from this thread (http://stackoverflow.com/questions/12928103/sdwebimage-process-images-before-caching) – BrianAllenToronto Nov 25 '13 at 02:09
  • @BrianAllenToronto No, I don't think there's anything that you should be doing with autorelease pool (that is useful when tweaking high-water mark, not for mitigating total memory used when done). But resizing images to the appropriate size for your UI will have _huge_ benefit. That's definitely a good line to pursue. If it's useful, [these are the image sizing routines I use](http://stackoverflow.com/questions/10491080/uiimage-resizing-not-working-properly/10491692#10491692). – Rob Nov 25 '13 at 03:09
  • very nice class you wrote there! I used something similar but a bit less organized than your class. You were right, resizing made a huge difference! – BrianAllenToronto Nov 25 '13 at 03:58
0

It appears that this simple change of just resizing images before they are loaded from sdwebimage cache has solved my problem. SDWebImage process images before caching I must have been running into some kind of iOS burst limit on my iPhone 4 in terms of how many large sized images can be loaded quickly at once.

My latest memory profile shows that the cache appears to be dumping correctly after hitting a high memory threshold and it only seems to maybe get close to the level for a crash when it hits something like 4 or 5 gifs in a row.

Thanks very much for the help!

Community
  • 1
  • 1