1

I'm not well versed in objective-c, so forgive me if this is a stupid question. I've created a background thread to parse a list of files that exists in a directory, where the files in the directory can change at any time.

I call "contentsOfDirectoryAtPath" in every iteration of the loop, and my allocations suddenly go over 300mb. I can't figure out how to get ARC to release the returned array. Can someone maybe point me in the right direction here?

-(void) offlineModeThread
{

    NSString *dataDir = [ViewController getDataDirectory];
    NSFileManager *nfm = [NSFileManager defaultManager];

    while(1)
    {
        NSArray *files = [nfm contentsOfDirectoryAtPath:dataDir error:nil];
        /*
        if(files == nil)
            break;

        if([files count] <= 0)
        {
            files = nil;
            [NSThread sleepForTimeInterval: 5.0f];
            continue;
        }

        if(![ViewController obtainLock])
        {
            files = nil;
            continue;
        }
        */
        //[ViewController releaseLock];
        files = nil;
    }

}

As you can see, i've tried releasing the array by setting 'files' to nil, but it doesn't work.

Gogeta70
  • 881
  • 1
  • 9
  • 23
  • do you use ARC project? – Joey Nov 22 '13 at 01:07
  • As a side note: [NSFileManager defaultManager] is not thread safe, use [[NSFileManager alloc] init] instead. – San Nov 22 '13 at 01:18
  • @San I would read [the documentation](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/nsfilemanager_Class/Reference/Reference.html#//apple_ref/doc/uid/20000305-SW19) before making a claim like that. – CodaFi Nov 22 '13 at 01:21
  • Why get the contents of the directory every iteration? Just move the assignment outside the while loop. – CodaFi Nov 22 '13 at 01:27
  • @CodaFi Yes [NSFileManager defaultManager] thread safe if we are not setting delegate to NSFileManager. Accepted! – San Nov 22 '13 at 01:38
  • where is your autorelease pool? without one objects haven't leaked, they just haven't been released "yet" – Grady Player Nov 22 '13 at 01:52

2 Answers2

1

how are you creating this thread? NSThread? NSOperation and NSOperationQueue? GCD?

For NSThread and NSOperation, you're supposed to create your own autorelease pool as part of setting up the thread. That way temporary objects get managed correctly within that thread.

I believe GCD takes care of this detail for you.

In newer versions of Objective C (Objective C 2.0, I believe) you should use the new syntax

@autoreleasepool
{
   //Code to use a local autorelease pool
}

For loops that generate a ton of temporary objects, you can create a local autorelease pool for any block of code. You could refactor your code like this:

while(1)
{
  @autoreleasepool
  {
    NSArray *files = [nfm contentsOfDirectoryAtPath:dataDir error:nil];
    /*
    if(files == nil)
        break;

    if([files count] <= 0)
    {
        files = nil;
        [NSThread sleepForTimeInterval: 5.0f];
        continue;
    }

    if(![ViewController obtainLock])
    {
        files = nil;
        continue;
    }
    */
    //[ViewController releaseLock];
    files = nil;
  }
}

That would cause it to create a new autorelease pool for each iteration of the loop, and drain it at the end (thus releasing all temporary objects created in that iteration.)

By the way, if you're using NSThread, don't. Learn how to use GCD instead. GCD (Grand Central Dispatch) makes MUCH more efficient use of system resources than NSThread. NSThread is based on POSIX threads, which are very expensive to create, and tie up physical memory on the device for the life of your application.

NSOperationQueues have been refactored in recent OS versions to use GCD under the covers, so they are similarly efficient internally, although harder to use than GCD for most things.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • Thank you for this answer, adding an autorelease pool did the trick. I am creating this thread using NSThread. You said the NSThread takes up physical memory for the life of the application, but does that really matter if this thread is supposed to run for the life of the application? In either case, i'll take a look into GCD - thanks for the pointer! – Gogeta70 Nov 22 '13 at 16:28
0

Well, I think that the answer is obviously. Take a look in the documentation.

Many programs create temporary objects that are autoreleased. These objects add to the program’s memory footprint until the end of the block. In many situations, allowing temporary objects to accumulate until the end of the current event-loop iteration does not result in excessive overhead; in some situations, however, you may create a large number of temporary objects that add substantially to memory footprint and that you want to dispose of more quickly. In these latter cases, you can create your own autorelease pool block. At the end of the block, the temporary objects are released, which typically results in their deallocation thereby reducing the program’s memory footprint.

And you can see this question.

So the solution is using your @autoreleasepool{} because you are creating many temporary object.

Community
  • 1
  • 1
dcorbatta
  • 1,893
  • 19
  • 15