Jordan Smith
Jordan Smith

Reputation: 10378

Closure variables not deallocating

The easiest way for me to describe the problem is to show with a small example.

//In a swift file

myObjectiveCObject.setCallbackBlock {(object: AnyObject!) -> Void in

    var chunkOfMemory = //fill up var with memory stuff. Self is never referenced.

}

myObjectiveCObject.startParsing()


//In the objective-c class file

@property (nonatomic, copy) MyBlockType callbackBlock;

- (void)startParsing {

    //loop around thousands of times calling
    self.callbackBlock(someNewObject)

}


The memory allocated from each closure call doesn't get released until after I'm done with the objective-c object. Surely the expected behaviour would be for the memory to be freed after each closure call?

Upvotes: 3

Views: 287

Answers (1)

Rob
Rob

Reputation: 437381

This sort of issue can arise if one instantiates autorelease objects, but doesn't drain the autoreleasepool periodically. Generally the pool won't be drained until you yield back to the run loop (or the operation or dispatched task finishes). Thus, you always have to be very careful about looping thousands of times. (See Use Local Autorelease Pool Blocks to Reduce Peak Memory Footprint section of the Advanced Memory Management Programming Guide: Using Autorelease Pool Blocks.)

One generally controls this peak memory usage by creating (and thereby draining) your own local autoreleasepool:

- (void)startParsing {

    // loop around thousands of times

    for ( ... ) {  
        @autoreleasepool {
            // do whatever

            self.callbackBlock(someNewObject)
        }
    }    
}

There's an equivalent Swift function, unsurprisingly called autoreleasepool(), too, so if you knew that the autorelease objects were created by something that your closure created, you could tackle it there, too. Without seeing the code or analyzing it in Instruments, it's hard to guess which is creating the autorelease object. (Note, yes, I know that native Swift objects generally aren't autoreleased, but you can't vouch for whatever code your Swift closure calls, which is why Swift provides the autoreleasepool function.)

If you really want to diagnose this, you can run this in Instruments, and pausing it before startParsing finishes, and looking at what hasn't been released, and back-track to where those objects were instantiated, and that would help you diagnose where the autorelease objects were created, thereby confirming where you need to add your pool.

Clearly, when dealing with large loops, there are other sources of these sorts of memory issues, too, besides autorelease objects (e.g. holding too many large assets (images or the like) in memory at the same time, recursively calling functions, etc.). But I assume you've reviewed your code for anything obvious like that. So autorelease objects are another thing to check.

Upvotes: 2

Related Questions