Search code examples
iosobjective-cnsdatensdateformatter

Using dateFromString api of date formatter in a for loop giving huge memory issues


I created an instance of NSDateFormatter outside the for loop( or can say an instance variable) and then used it in a for loop as [dateFormatter dateFromString]. This is giving me huge memory issues. I tried lowering the huge memory allocations using the @autorelease directive where I have moved the for loop to the autorelease block. Also I set the dateFormatter instances to nil outside the autorelease block. Looking for a better approach to lower the memory consumption due to dateFormatter inside a for loop.

-(void)start {
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:10.0 target: self selector:@selector(refresh) userInfo: nil repeats: YES];
}

-(void)refresh
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        // Code for Fetching data from server and parsing it
        CustomObject *obj = [Parser parseData:dictionary];
    });
}

@implementation Parser

+(CustomObject *)parseData:(NSDictionary *) dictioanry {

    NSArray *array;
    array = [dictionary objectForKey:@"someKey"];

    NSDateFormatter *df = [[NSDateFormatter alloc] init];
    [df setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZ"];
    @autoreleasepool
    {
        for ( NSDictionary *dic in array)
        {
            NSDate *date = [df dateFromString:[dic objectForKey:@"key"]];
            nestedObj.startTime = date;
   // construct and store the custom Objects
   // add the nested objects to an array
            [obj.arr add:nestedObj];
            date = nil;
        }
    }
    df = nil;
    return obj;
}
@end

Solution

  • Your @autoreleasepool is in the wrong place. You ned to be draining the pool at the end of each loop:

        for ( NSDictionary *dic in array)
        {
            @autoreleasepool {
                NSDate *date = [millisecondDateFormatter dateFromString:[dic objectForKey:@"key"]];
                obj.nestedObj.startTime = date;
                date = nil;
            }
        }
    

    That said, I assume your real code is significantly different. This code doesn't make any sense. You're overwriting the same obj.nestedObj.startTime over and over again. Only the last loop iteration is going to matter. But perhaps that is a transcription error when you created the example.

    Note that there is no reason to use x = nil all over the place like this. That will happen automatically, and you should generally allow it to do so.


    EDIT:

    Just to touch on @trojanfoe's question in the comments (nothing here is that important, it was just an interesting question). We can explore Foundation a little bit to see what it's doing. After a few twists and turns, the call to dateFromString: eventually winds up in a method called _getObjectValue:, which eventually does something like this in the OS X version:

    ...
    NSDate *date = (NSDate *)CFDateFormatterCreateDateFromString(NULL, (CFTypeRef)self, ...);
    if (date != nil) { CFMakeCollectable((CFTypeRef)date); }
    [date autorelease];
    ...
    

    We could intuit this without diving into the assembly by considering that getObjectValue:forString:range:error: is the most general form, and its the one dateFromString references, so everything probably bottlenecks through it (and this is in fact true). And the Swift interpretation of this method could give you insight that it's probably going to have an autorelease somewhere:

    func getObjectValue(_ obj: AutoreleasingUnsafePointer<AnyObject?>, forString string: String!, range rangep: UnsafePointer<NSRange>, error error: NSErrorPointer) -> Bool
    

    In practice, of course, you almost never think this hard about the problem :D You should assume that any Cocoa method you call may generate autoreleased objects, and behave accordingly. Even if you can show that it doesn't in a particular version of Cocoa, Apple is free to change that in future versions, so you should always expect them.