Arc Memory Leaks

ARC memory leaks

You're probably running this code on a background thread, and don't have an autorelease pool in place. ARC will still autorelease objects for you on occasion, and if you're calling into Apple frameworks, they may still be non-ARC, so they definitely could be autoreleasing objects for you. So you still need an autorelease pool in place.

Cocoa creates an autorelease pool for you on the main thread, but doesn't do anything for you on background threads. If you're going to kick something off onto a background thread without using NSOperation or something, you'll want to wrap that thread in an @autoreleasepool, like so:

- (void)doSomething {
[self performSelectorInBackground:@selector(backgroundSomething)];
}

- (void)backgroundSomething {
@autoreleasepool {
NSLog(@"Here I am in the background, doing something.");
myArray = [[NSMutableArray alloc] init];
// etc.
}
}

Objective C / iOS: Memory release with ARC (memory leak)

A few things could be going wrong:

  1. You might not actually have ARC enabled. You should double-check that. Simplest way is to throw in a -retain in your code and make sure that throws a compiler error.

  2. ARC doesn't necessarily prevent objects from entering the autorelease pool. It tries to catch that if it can, but it doesn't make guarantees. Notably, at -O0 (no optimization), it frequently does not prevent objects from entering the autorelease pool. This is most likely what's going on for you.

  3. Even at higher optimization levels, ARC-enabled code is still not guaranteed to catch autoreleases.

If you stick an @autoreleasepool{} inside of your for loop you'll find the memory usage should go away. Alternatively, instead of using [NSMutableString string], you could try [NSMutableString new], which doesn't use the autorelease pool at all* but should otherwise behave identically in ARC code.

* well, NSMutableString is free to use the autorelease pool internally if it wants to

ARC is enabled but having Memory Leak (Objective C)

The memory is not exactly leaked: it is very much ready to be released, but it never has a chance to be.

ARC builds upon the manual memory management rules of Objective-C. The base rule is that "the object/function that calls init owns the new instance", and the owner must release the object when it no longer needs it.

This is a problem for convenience methods that create objects, like [NSData dataWithContentsOfFile:]. The rule means that the NSData class owns the instance, because it called init on it. Once the value will be returned, the class will no longer need the object, and it would need to release it. However, if this happens before the callee gets a chance to retain the instance, it will be gone before anything had a chance to happen.

To solve this problem, Cocoa introduces the autorelease method. This method transfers the ownership of the object to the last autorelease pool that was set up. Autorelease pools are "drained" when you exit their scope.

Cocoa/AppKit/UIKit automatically set up autorelease pools around event handlers, so you generally do not need to worry about that. However, if you have a long-running method, this becomes an issue.

You can declare an autorelease pool using the @autoreleasepool statement:

@autoreleasepool
{
// code here
}

At the closing bracket, the objects collected by the autorelease pool are released (and possibly deallocated, if no one else has a reference to them).

So you would need to wrap the body of your loop in this statement.

Here's an example. This code "leaks" about 10 megabytes every second on my computer, because the execution never leaves the @autoreleasepool scope:

int main(int argc, const char * argv[])
{
@autoreleasepool
{
while (true)
{
NSString* path = [NSString stringWithFormat:@"%s", argv[0]];
[NSData dataWithContentsOfFile:path];
}
}
}

On the other hand, with this, the memory usage stays stable, because execution leaves the @autoreleasepool scope at the end of every loop iteration:

int main(int argc, const char * argv[])
{
while (true)
{
@autoreleasepool
{
NSString* path = [NSString stringWithFormat:@"%s", argv[0]];
[NSData dataWithContentsOfFile:path];
}
}
}

Creating objects in the loop condition is awkward for long loops because these are not picked up by the inner @autoreleasepool. You will need to get these inside the @autoreleasepool scope as well.

Returning

Whenever we return an object (maybe to Swift), we need to register into nearest @autoreleasepool block (by calling autorelease method to prevent memory-leak, according to ownership-rules), but nowadays ARC does that automatically for us;

Whenever ARC disabled; after using alloc and/or init, call autorelease manually, like:

- (NSString *)fullName {
NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@",
self.firstName, self.lastName] autorelease];
return string;
}

Instruments Memory Leak With ARC Confusion

Well, the leak is then probably originating somewhere from inside the framework (UIKit or deeper). If so, then there's not much you can do about it. Either it's some sort of "side effect" from inside your application that shows up later and deep inside UIKit ([UIImage imageWithCGImage:scale:orientation]), or it's some problem with UIKit itself. But in the end it's hard to tell for sure!

Using ARC still doesn't guarantee 100% memory-leak free code! ;)

Memory Leak with ARC

ARC does not manage C-types, of which CGImage may be considered. You must release the ref manually when you are finished with CGImageRelease(image);

+(void)setup {
UIImage* spriteSheet = [UIImage imageNamed:@"mySpriteSheet.png"];
CGRect rect;
animation = [NSMutableArray arrayWithCapacity:numberOfFramesInSpriteSheet];
int frameCount = 0;

for (int row = 0; row < numberFrameRowsInSpriteSheet; row++) {
for (int col = 0; col < numberFrameColsInSpriteSheet; col++) {
frameCount++;
if (frameCount <= numberOfFramesInSpriteSheet) {
rect = CGRectMake(col*frameHeight, row*frameWidth, frameHeight, frameWidth);
//store our image ref so we can release it later
//The create rule says that any C-interface method with "create" in it's name
//returns a +1 foundation object, which we must release manually.
CGImageRef image = CGImageCreateWithImageInRect(spriteSheet.CGImage, rect)
//Create a UIImage from our ref. It is now owned by UIImage, so we may discard it.
[animation addObject:[UIImage imageWithCGImage:image]];
//Discard the ref.
CGImageRelease(image);
}
}
}
}

Memory leak (ARC)

Well, the problem seems to be this:

  • if you have a strong reference to self in your block inside -addAnimation:, you get a retain-cycle
  • if you have a weak reference to self in your block inside -addAnimation:, self is released too early, and your completion block is pointless.

What you need is a way to break the retain-cycle at runtime:

// Version 1:
- (BOOL)addAnimation:(HyAnimation*)animation
{
@synchronized(self) {

if (self.isRunning) {
return NO;
}

[self.animations addObject:animation];

// Self-subscribe for updates so we know when the animations end
[animation addCompletionFunction:^(HyAnimation * anim) {

static unsigned int complete = 0;

// We are only interested in knowing when the animations complete, so we can release the locks
++complete;

if (complete == [self.animations count]) {

// Reset, so the animation can be run again
complete = 0;

@synchronized(self) {

// Release all locks
[self.locks setLocked:NO];

// Break retain cycle!!
[self.animations removeAllObjects];

// IF it still doesn't work, put a breakpoint at THIS LINE, and
// tell me if this code here runs ever.

// Done
self.isRunning = NO;
}
}
}];

return YES;
}
}
  1. self points to an array
  2. the array points to an animation
  3. the animation points to a block
  4. the block points to self

When one of these connections is broken, the whole cycle breaks. The easiest way to break the cycle is to destroy the array pointer (1) by calling -removeAllObjects on the array.


BTW, another problem with your code is the static variable in your HyAnimationCollection. This will be a problem as soon as you have more than one HyAnimationCollection object running at the same time. I would just create an instance variable unsigned int _completeCount;:

(you could also create a synthesized property instead, but I prefer an iVar in this case:

@interface HyAnimationCollection .....
{
unsigned int _completeCount; //is automatically initialized to 0 by the objc-runtime.
}

...

)

and in the implementation file:

// Version 2:
- (BOOL)addAnimation:(HyAnimation*)animation
{
@synchronized(self) {

if (self.isRunning) {
return NO;
}

[self.animations addObject:animation];

// Self-subscribe for updates so we know when the animations end
[animation addCompletionFunction:^(HyAnimation * anim) {
@synchronized(self) {
// We are only interested in knowing when the animations complete, so we can release the locks
++_completeCount;

if (_completeCount == [self.animations count]) {

// Reset, so the animation can be run again
_completeCount = 0;

// Release all locks
[self.locks setLocked:NO];

// Break retain cycle!!
NSLog(@"Breaking retain cycle (HyAnimationCollection)");
[self.animations removeAllObjects];

// Done
self.isRunning = NO;
}
}
}];

return YES;
}
}

2.: the inner @synchronized block should be wrapped around the whole block, if it is needed at all. Just putting it around the unlocking doesn't provide any thread safety. (To test it, you can also put the line assert([NSThread isMainThread]); at the beginning of that block: if it doesn't crash, it means that you can omit the whole @synchronized(self) thing. The code then becomes:) The outer @synchronized block should probably stay.

// Version 3.
- (BOOL)addAnimation:(HyAnimation*)animation
{
@synchronized(self) {

if (self.isRunning) {
return NO;
}

[self.animations addObject:animation];

// Self-subscribe for updates so we know when the animations end
[animation addCompletionFunction:^(HyAnimation * anim) {
assert([NSThread isMainThread]);
// We are only interested in knowing when the animations complete, so we can release the locks
++_completeCount;

if (_completeCount == [self.animations count]) {

// Reset, so the animation can be run again
_completeCount = 0;

// Release all locks
[self.locks setLocked:NO];

// Break retain cycle!!
NSLog(@"Breaking retain cycle (HyAnimationCollection)");
[self.animations removeAllObjects];

// Done
self.isRunning = NO;
}
}];

return YES;
}
}

My tip: try version 3 first. if it crashes use version 2 instead. (the scheme should be DEBUG, not RELEASE during development, otherwise assert() may not work and you may get strange problems later.) hope it works...

Memory leak using ARC GPUImage

First, I'd suggest running your code through the static analyzer ("Analyze" on Xcode's "Product" menu, or press shift-command-B), which can be useful in identifying issues in Objective-C code. Make sure you have a clean bill of health from the Analyzer before proceeding. With ARC, there might not be too many issues here, but it's worth checking, just to make sure.

Second, when you get a leak report, that's not necessarily what caused the leak. It's just showing you where the leaked object was originally created, so you can then go through your code and figure out why that particular object leaked. For example, I ran an example through "Leaks" and it directed me to this routine:

Sample Image

That isn't terribly illuminating. It's nice to know what leaked, but I'd rather find out why the object leaked, not just where the leaked object was originally allocated.

But when I ran the app (not through Instruments, but just run it in the debugger) and tapped on the "Debug memory graph" button, Sample Image, I could then tap on my object that should have been released in the left panel and I can now see what object is maintaining the strong reference. In this case, I can see the strong reference cycle that I had accidentally established:

Sample Image

Armed with this information, I can track down where these strong references were established and figure out why they are still present. Or, in this example, I would track down why I have a strong reference cycle and figure out which of those references need to be weak.



Related Topics



Leave a reply



Submit