core-dataautomatic-ref-countingautorelease

Objective C For loops with @autorelease and ARC


As part of an app that allows auditors to create findings and associate photos to them (Saved as Base64 strings due to a limitation on the web service) I have to loop through all findings and their photos within an audit and set their sync value to true.

Whilst I perform this loop I see a memory spike jumping from around 40MB up to 500MB (for roughly 350 photos and 255 findings) and this number never goes down. On average our users are creating around 1000 findings and 500-700 photos before attempting to use this feature. I have attempted to use @autorelease pools to keep the memory down but it never seems to get released.

    for (Finding * __autoreleasing f in self.audit.findings){
        @autoreleasepool {
            [f setToSync:@YES];
            NSLog(@"%@", f.idFinding);

        for (FindingPhoto * __autoreleasing p in f.photos){
            @autoreleasepool {
                [p setToSync:@YES];
                p = nil;
            }

        }
        f = nil;

        }
    }

The relationships and retain cycles look like this

Audit has a strong reference to Finding

Finding has a weak reference to Audit and a strong reference to FindingPhoto

FindingPhoto has a weak reference to Finding

What am I missing in terms of being able to effectively loop through these objects and set their properties without causing such a huge spike in memory. I'm assuming it's got something to do with so many Base64 strings being loaded into memory when looping through but never being released.


Solution

  • So, first, make sure you have a batch size set on the fetch request. Choose a relatively small number, but not too small because this isn't for UI processing. You want to batch a reasonable number of objects into memory to reduce loading overhead while keeping memory usage down. Try 50 or 100 and see how it goes, then consider upping the batch size a little.

    If all of the objects you're loading are managed objects then the correct way to evict them during processing is to turn them into faults. That's done by calling refreshObject:mergeChanges: on the context. BUT - that discards any changes, and your loop is specifically there to make changes.

    So, what you should really be doing is batch saving the objects you've modified and then turning those objects back into faults to remove the data from memory.

    So, in your loop, keep a counter of how many you've modified and save the context each time you hit that count and refresh all the objects that were processed so far. The batch on the fetch and the batch size to save should be the same number.