6

I appear to have a memory management issue when I create UIImage instances the CGImages are not being released.

I have a paging UIScrollView to scroll through a series of JPG images. Below is my entire class that is a page view in the paging scroll view.

The code is being run on the main thread. The code uses ARC. I have tried loading the images using imageWithContentsOfFile: (returns an autoreleased object) as well as initWithContentsOfFile:(returns a retained object). I have tried @autoreleasepool and using performSelectorOnMainThread to ensure that the code is being run on main thread as suggested in other posts.

When scrolling through the images memory usage just grows until the app gets terminated as illustrated in the screenshot from instruments. Note the high allocation to image io.

Screenshot showing virtual memory usage

Virtual Memory Tracker

In the following screenshot it can be seen that the GTGImageScrollerPageViews, UIImageViews and UIImages are being deallocated. Notice there are Transitory objects for these numbering in the high 300's. However the CGImages are not being released and the number of CGImages living is in the high 400's and 0 Transitory.

Screenshot showing allocations

Allocations

EDIT: Previously I had been recycling and re-using GTGImageScrollerPageView instances in the ScrollView as is the common pattern for scrollviews like this. In order to simplify while trying to debug this problem I allow the entire GTGImageScrollerPageView to be deallocated after it has been displayed in the ScrollView. As you can see in the second image above, there are only 4 GTGImageScrollerPageView living and 377 transitory, there are also 388 UIImageViews and 389 UIIMages listed as transitory so it appears that the UIImageViews and UIImages are being deallocated fine.

If I manually release the CGImage with CGImageRelease (Commented out in the code below) the CGImages are released. I know that I should not do this because I do not own the CGImage but this is useful to verify that this is where the problem is occurring. The screenshots below show the same code tested in Instruments but with CGImageRelease uncommented.

Screenshot showing virtual memory usage with CGImageRelease used

Virtual memory usage

Screenshot showing allocations with CGImageRelease used

enter image description here

In the profiling outputs where CGImageRelease is used, you can see that the correct number of CGImage objects are Living and Transitory and that memory does not grow unbounded. Furthermore the app does not crash during usage with CGImageRelease used.

If this were some system caching of CGImage then it should release the memory when a memory warning is raised, but it doesn't. Memory just continues to grow.

What here could be causing this unbounded growth in memory?

Here is the code for the page view

EDIT: In response to comments I have updated the code to simplify further thus eliminating distractions such as ivars and properties that are not needed to demonstrate the problem. The problem remains unchanged and the results are the same when profiling. I have also added in the NSLog which also outputs the thread. I do see the dealloc being called as expected on the GTGImageScrollerPageView and it is always thread #1 and the call to displayAspectThumbImage is always on thread #1.

I really dont believe that there is anything wrong with the code that is presented here and this is confirmed by the generous effort of Rob. Something else is causing this but all the code related to loading and displaying the image is here; there is no other code in effect here. The only other notable thing that I could think of raising is that the displayAspectThumbImage method is called from scrollViewDidScroll and scrollViewDidEndDecellerating methods of the scrollview delegate but the fact that the methods are called on the main thread should exclude autorelease issues due to being run on another thread.

I have checked and NSZombies is not enabled and there are no Zombies increasing my memory usage. Indeed when the CGImageRelease method is called the memory usage is pretty flat as can be seen in the screenshot above.

@implementation GTGImageScrollerPageView

- (void)dealloc {

    NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSThread currentThread]);

 }


  - (void)displayAspectThumbImage:(NSString *)path {

      NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSThread currentThread]);

      UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.bounds];
      [self addSubview:imageView];

      UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
      [imageView setImage:image];

      //If uncommented CGImages are disposed correctly
      //CGImageRelease([imageView.image CGImage]);

}


@end

Tested on:

iOS 6.1.4

iOS 5.0.1

Jet Basrawi
  • 3,185
  • 2
  • 15
  • 14
  • You're right that releasing the `CGImage` is not the right solution. You're showing us the creation of the image view, but do you ever remove it? When scrolling through images, as you add the new image view, you should be removing the old one. Are you doing that? – Rob May 28 '13 at 23:07
  • Hi Rob, I edited the question to make that clear, but you can see from the object summary of the allocations view, only 4 GTGImageScrollerPageViews are living and 377 Transitory illustrates that the object is being deallocated. Same for UIImageViews and UIImages. – Jet Basrawi May 29 '13 at 06:55

3 Answers3

4

Really sorry to be answering this question myself but thought it would be useful to share my experience.

It turned out to be a category on UIImage that was part of a third party library I was using. I wont name the library as I have tested with the latest version and the problem is not there so there is nothing to gain by naming the library.

It is particularly strange as the library was not being used anywhere near the code that was leaking. It was only being used in one place in the whole project, but any time I used a UIImage it seems that it was affecting the UIImage instance. The mere presence of this category in the project was enough. This came as a real surprise.

The way I solved this was to begin by simulating the scenario in a new project and found that the code was not leaking there. I then migrated the code a little at a time and when I moved the categories across the leak appeared.

Huge thanks to Rob for his generous efforts to help me with this and even though he did not provide the solution directly, talking to him was really helpful in solving the problem. Nice to know there are such cool people out there.

Jet Basrawi
  • 3,185
  • 2
  • 15
  • 14
  • We are facing the same memory issue on our project and couldnt find any solution yet.. We are using multiple image libraries on our project. I checked with breakpoints and none of that methods getting called anytime, but memory is not getting released..Do you mind sharing the library name you had problem.. may be helpful for us to remove that and check.. Any other suggestions?? – Anil Sivadas Jan 05 '16 at 17:19
  • 1
    Agreed. I am encountering similar issues and this 'answer' doesn't help in anyway because it avoids the one important detail.. – Thomas Clowes Jul 21 '16 at 17:34
3

I did a simple infinite scroller using your code and after scrolling through nearly 100 images the memory usage was, as one would have expected, thoroughly uneventful:

allocations

vm

Looking at your source, I would have recommended a few little things (e.g. I would have put aspectImageView into a private class extension rather than the .h, I assume you're setting pageIndex from the calling routine but I'd add it as a parameter of displayAspectThumbImage, I would have made aspectImageView a weak property and refactor the code accordingly (e.g. create image view, add it as subview, then set the weak imageview property to point to this object), etc.), but none of those have direct bearing on your issue here.

Bottom line, your problem does not rest in the code you've shared with us.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Hi Rob, thanks for your efforts on this. I fully agree there is nothing wrong with the code here. However apart from passing in a path for the image to load there is no other code involved in the loading and displaying of the image. I wasn't really expecting the code here to be the issue. I was hoping that someone may be aware of some system behaviour, or some other factor that I had not considered, that may be causing this. – Jet Basrawi May 29 '13 at 14:11
  • @JetB Are you seeing your `dealloc` of `GTGImageScrollerPageView` getting called? Do you have any code that is accessing the `aspectImageView` other than the above code? (Btw, that's why I suggested making that property a private class extension, so you'd know nothing could possibly messing with it.) – Rob May 29 '13 at 14:15
  • I had a similar problem (And still have that problem) of this behaviour elsewhere in my project where I have a custom image picker type scroller. Again with no apparent cause. I had chosen to ignore this for now but it appeared again on this image scroller functionality and could not now ignore as want to release soon. Again there is nothing apparently wrong with that code and when I simulated in a test project all worked fine. – Jet Basrawi May 29 '13 at 14:15
  • @JetB I also assume you have zombies, and all of the other memory diagnostic stuff turned off? – Rob May 29 '13 at 14:17
  • Hi Rob, I do see the dealloc logged from the Page View. I will go and try your suggestion about moving the declaration. I have also tried in the past having no ImageView instance var or property, and just allowing the only reference to the imageview being the one created when added to the view as a subview. Will check zombies too. – Jet Basrawi May 29 '13 at 14:18
  • @JetB If worst comes to worse, if your diagnostics don't turn up anything, you can upload your project somewhere and I'll take a closer look. – Rob May 29 '13 at 14:24
  • I have followed your recommendations with a few further additions to simplify the code being tested. I have posted the amended code in the question. If nothing turns up I will have a chat with you about sharing the full code and a deal for you if you manage to solve it. Thanks for your help. – Jet Basrawi May 29 '13 at 15:33
  • @JetB let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/30850/discussion-between-rob-and-jetb) – Rob May 29 '13 at 16:05
0

Try to use property instead of ivar.

@property (nonatomic, strong) UIImageView *aspectImageView;

alexhajdu
  • 410
  • 5
  • 13
  • Hi Alex, I have tried this with a property instead of an ivar and updated the code in the question to reflect this. Unfortunately the behaviour is exactly the same. I dont really see why using a property with strong semantics would be any different to using an ivar here though. The problem isn't that the UIImageView isn't being released correctly, the UIImageView is being released. The problem is that the UIImage in the UIImageView does not appear to be releasing it's CGImage. – Jet Basrawi May 29 '13 at 07:53
  • And where do you release the UIImageViews? Or you are checking this in GTGImageScrollerPageView dealloc? If yes, try to put all UIImage objects to array and [_myImagesArray removeAllObjects]. It should work. – alexhajdu May 29 '13 at 08:09
  • Hi Alex, I am using ARC so I should not explicitly release the UIImageViews. The UIImageViews are getting released and also the UIImages that are displayed in them are getting released. Even if I do not set the image on the UIImageView (just load the UIimage and do nothing with it) the same thing happens: the UIImage gets released but the CGImage in there does not. – Jet Basrawi May 29 '13 at 08:55