ARC is enabled but having Memory Leak (Objective C) - objective-c

As you can see, the code below isnt doing much (all commented out) more than enumerating over a set of files, however, my memory usage is growing to over 2 GB after 40 seconds of running the function below which is launched by pressing a button on the UI.
I can run the UI for hours, and before pressing the button, the memory usage does not exceed 8MB.
Given that ARC is turned on, what is holding on to the memory?
removed original code as the edit below made no differance.
EDIT:
Attempted #autoreleasepool{ dispatch_asyny ... } and permutations of that around the while and inside the while loop which had no effect.
Here is the code with autorelasepool added and cleaned up
-(void) search{
self.dict = [[NSMutableDictionary alloc] init];
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:#"/tmp/SeaWall.log"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *bundleRoot = #"/";
NSFileManager *manager = [NSFileManager defaultManager];
NSDirectoryEnumerator *direnum = [manager enumeratorAtPath:bundleRoot];
NSString *filename;
while ((filename = [NSString stringWithFormat:#"/%#", [direnum nextObject]] ) && !self.exit) {
#autoreleasepool {
NSString *ext = filename.pathExtension;
if ([ext hasSuffix:#"so"] || [ext hasSuffix:#"dylib"] ) {
if (filename == nil || [NSURL URLWithString:filename] == nil) {
continue;
}
NSData *nsData = [NSData dataWithContentsOfFile:filename];
if (nsData != nil){
NSString *str = [nsData MD5];
nsData = nil;
[self writeToLogFile:[NSString stringWithFormat:#"%# - %#", [filename lastPathComponent], str]];
}
}
ext = nil;
} // end autoreleasepool
}
[fileHandle closeFile];
[self ControlButtonAction:nil];
});
}

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;
}

Memory needs to be released by an autorelease pool.
Otherwise it will be locked up as you are experiencing and it will leak.
In your loop put:
#autoreleasepool { /* BODY */ }

Related

NSData dataWithContentsOfURL memory leak

A Command Line Tool project was generated in XCode 5.1 for OSX, with the following simple code only to prove NSData memory leak.
The NSData object is never released. Every new instance in the loop increments memory leak. Autorelease doesn't work. Some of the direct release attempts results to syntax error. Any advice?
Sourrounding the NSData allocation with #autoreleasepool didn't help.
If I change NSData object to an NSString object, it also causes memory leak. So it seems to be a general memory deallocation problem in this Command line tool environment.
Returning from the memoryLeak method to the caller, the memory is still unrealased.
+ (void) memoryLeak {
NSURL *tileURL = [NSURL URLWithString:#"http://c.tile.openstreetmap.org/9/0/0.png"];
for (int i=0; i < 10; i++) {
NSData *tile = [NSData dataWithContentsOfURL:tileURL];
}
}
Try updating your code to:
+ (void) memoryLeak {
#autoreleasepool {
NSURL *tileURL = [NSURL URLWithString:#"http://c.tile.openstreetmap.org/9/0/0.png"];
for (int i=0; i < 10; i++) {
NSData *tile = [NSData dataWithContentsOfURL:tileURL];
}
}
// Memory should be cleaned up here
}
Does it still leak? If so looks like a bug, if not, you were possibly detecting leaks before the autorelease pool had a chance to clean it up.

Leaderboard Requests, Nested Blocks, and Retain Cycles

I have developed a leaderboard display class for my iPhone game. The class has the following instance method.
-(void)displayScoresWithRequest:(CBLeaderboard*)request completionHandler:(void(^)())completionHandler
{
if (request_ != nil)
return;
request_ = [[CBLeaderboard alloc] init];
[request_ setCategory:[request category]];
[request_ setPlayerScope:[request playerScope]];
[request_ setTimeScope:[request timeScope]];
[request_ setRange:[request range]];
__block CBLeaderboardDisplay* blockSelf = self;
[request_ loadScoresWithCompletionHandler:^(NSArray* scores, NSError* error)
{
blockSelf->request_ = nil;
NSUInteger scoresCount = [scores count];
if (scoresCount == 0 && error != nil)
return;
NSMutableArray* playerIDs = [NSMutableArray array];
for (GKScore* score in scores)
[playerIDs addObject:[score playerID]];
[GKPlayer loadPlayersForIdentifiers:playerIDs withCompletionHandler:^(NSArray* players, NSError* error)
{
if (scoresCount > [players count] && error != nil)
return;
[blockSelf displayScores:scores players:players];
completionHandler();
}];
}];
[request_ release];
}
As you can see, the method copies a leaderboard request, executes it, and calls the supplied completion handler. A layer in my game calls this method as follows.
-(void)refreshDisplay
{
CBLeaderboard* request = [[CBLeaderboard alloc] init];
[request setCategory:[[sharedGameCenterManager_ classicLeaderboard] category]];
[request setPlayerScope:GKLeaderboardPlayerScopeFriendsOnly];
[request setTimeScope:GKLeaderboardTimeScopeAllTime];
static NSRange kRequestRange = NSMakeRange(1, 3);
[request setRange:kRequestRange];
__block GJGameOver* blockSelf = self;
[display_ displayScoresWithRequest:request completionHandler:^
{
CGSize displayContentSize = [blockSelf->display_ contentSize];
displayContentSize.width = width(blockSelf) - 2.0 * kGJLabelPadding;
[blockSelf->display_ setContentSize:displayContentSize];
CGFloat displayHeight =
bottomEdge(blockSelf->multiplierLabel_) - topEdge(blockSelf->menu_) - 2.0 * kGJLabelPadding;
CGFloat displayScoreDisplaysCount = [blockSelf->display_ scoreDisplaysCount];
CGFloat displayLabelPadding =
(displayHeight - [blockSelf->display_ minContentSize].height) / displayScoreDisplaysCount;
[blockSelf->display_ setLabelPadding:MIN(floor(displayLabelPadding), kGJLabelPadding)];
static CGFloat kFadeInDuration = 2.0;
if ([blockSelf->display_ opacity] == 0)
[blockSelf->display_ runAction:[CCFadeIn actionWithDuration:kFadeInDuration]];
}];
[request release];
}
My game crashes when both the layer and hence display are deallocated and the request has not completed. When the request completes, it attempts to send a message to a deallocated instance and the crash ensues. Is it possible to cancel a leaderboard request? If not, is there any way I can avoid the crash without causing a memory leak?
In both of your blocks, you use __block to allow the block to reference self without retaining it. This is the problem, because you are doing an asynchronous operation, and if the block is executed after self has been deallocated, it is using a dangling pointer. The whole point of blocks retaining objects they capture is to keep them alive so the block can use it.
Not retaining self when making blocks is commonly done to avoid retain cycles. However, I don't see any retain cycles here:
The request_ in displayScoresWithRequest probably retains the block in displayScoresWithRequest
The block in displayScoresWithRequest retains self, the CBLeaderboardDisplay object
The block in displayScoresWithRequest retains the block from refreshDisplay
The block in refreshDisplay retains self, the GJGameOver object
The GJGameOver object retains display_, the CBLeaderboardDisplay object
However, the CBLeaderboardDisplay object does not retain its instance variable request_. (This code is extremely poorly written, as request_ is released at the end of the method but not set to nil. It should probably be made a local variable or something. And a boolean flag should be used if you want to check whether the code has run once or not.)

xcode ios ARC (automatic reference counting) regarding dispatch_async and nsdata

I have a beginner question about xcode ARC. The following code works without memory issue because the memory is freed by the ARC.
- (void)viewDidLoad
{
[super viewDidLoad];
// test nsmutabledata
dispatch_queue_t testQueue = dispatch_queue_create("testQueue", NULL);
dispatch_async(testQueue, ^{
while (1) {
NSMutableData *testData = [[NSMutableData alloc]initWithCapacity:1024*1024*5];
NSLog(#"testData size: %d", testData.length);
}
});
}
However, the following does not, and gives me memory allocation error after a few seconds.
+ (NSMutableData *) testDataMethod
{
NSMutableData *testDataLocal = [[NSMutableData alloc]initWithCapacity:1024*1024*5];
return testDataLocal;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// test nsmutabledata
dispatch_queue_t testQueue = dispatch_queue_create("testQueue", NULL);
dispatch_async(testQueue, ^{
while (1) {
NSMutableData *testData = [RootViewController testDataMethod];
NSLog(#"testData size: %d", testData.length);
}
});
}
Do I have the wrong understanding of ARC? I though the testDataLocal is counted once but goes out of the scope when the method exits. testData is another count but at the next iteration of the loop testData should have no count, and be freed by the system.
In the first bit of code, the NSMutableData object is released at the end of each loop iteration which avoids any memory issues.
In the second bit of code, the return value of the testDataMethod is most likely being autoreleased. Since your app in running in a tight loop, the autorelease pool in never given a chance to be flushed so you quickly run out of memory.
Try changing your second bit of code to this:
while (1) {
#autoreleasepool {
NSMutableData *testData = [RootViewController testDataMethod];
NSLog(#"testData size: %d", testData.length);
}
}

Huge memory footprint with ARC

App I'm working on uses ARC. I wanted it to process large files, so instead of loading files as a whole, I'm loading chunks of data using NSFileHandle readDataOfLength method. It's happening inside a loop which repeats until the whole file is processed:
- (NSString*)doStuff { // called with NSInvocationOperation
// now we open the file itself
NSFileHandle *fileHandle =
[NSFileHandle fileHandleForReadingFromURL:self.path
error:nil];
...
BOOL done = NO;
while(!done) {
NSData *fileData = [fileHandle readDataOfLength: CHUNK_SIZE];
...
if ( [fileData length] == 0 ) done = YES;
...
}
...
}
According to profiler, there are no memory leaks; however, my app eats a LOT of memory while it processes the file. My guess — autorelease comes only after I process the file. Can I fix it without switching to manual memory management?
Wrap the code within that loop with a autorelease pool.
while(!done)
{
#autoreleasepool
{
NSData *fileData = [fileHandle readDataOfLength: CHUNK_SIZE];
...
if ( [fileData length] == 0 )
{
done = YES;
}
...
}
};
readDataOfLength retuns autoreleased data and since you stick inside that loop and therefor its method, that autoreleased data that does not get released until your loop and the encapsulating method is done.

NSDATA writeToFile crashes without a reason

I am downloading images from a url using NSDATA and saving them to local file system using
NSData *dataForStorage = [NSData dataWithData:UIImagePNGRepresentation(img)];
BOOL saveResult=[ dataForStorage writeToFile:jpegFilePath options:NSDataWritingAtomic error:&error];
NSLog(#"Write returned error: %#", [error localizedDescription]);
My app crashes randomly without even giving a message, though some files are saved (again randomly). When I run the app in Debug mode, I frequently see "EXC_BAD_ACCESS" but continuing execution succeeds in saving some of the files.
This code is executed in background from:
[self performSelectorInBackground:#selector(loadImageInBackground:) withObject:arr];
Please suggest.
One of the problems in your code is that your running code in a thread without an autorelease pool but are using functions that would require one. Put the following code into the loadImageInBackground method:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// existing code
[pool drain];
This is probably just one of several problems. For further assistance, we need to see the stack trace of the crash.
Just a wild guess : arr is an autoreleased object, so, sometimes it gets deallocated before your selector gets called. Try using [arr copy] and release it after saving it.
I was having the EXACT same problem, but it turned out that the problem was something else: my URL was getting release prematurely. In the end this is what I did and it worked:
I made this call:
[self performSelectorInBackground:#selector(downloadData:) withObject:nil];
And this is the method:
// URL - (NSString) URL for file
// filePath - (NSString) save location on device
-(void)download:(NSString *)URL
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:URL]];
[data writeToFile:filePath atomically:YES];
[pool release];
}
So I think that your download code is correct, but there is some other variable that is getting deallocated early (possibly your path).
Hope this helps! I know the other answers on this page worked for me.