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:
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.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.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;
}
}
self
points to an array- the array points to an
animation
- the
animation
points to a block - 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:
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, , 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:
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
Rebuild an Nsarray by Grouping Objects That Have Matching Id Numbers
How to Call Presentviewcontroller from Within a Uicollectionviewcell
Error: Error Domain=Nsurlerrordomain Code=-1001 "The Request Timed Out."
Enable Application Cache in Wkwebview
Swift Version of Componentsseparatedbystring
How to Debug an iOS Extension (.Appex)
Synchronous Url Request on Swift 2
How to Run Nstimer in Background Beyond 180Sec in iOS 7
How to Add a Button to the Mkpointannotation
iOS 13.1 Crash in Avaudio Player
Nsdateformatter Milliseconds Bug
Firebase with Swift 3 Counting the Number of Children
Uncaught Exception: This Class Is Not Key Value Coding-Compliant
Why Is -Diddeselectrowatindexpath Not Being Called
Storyboard View Elements Greyed Out