The Danger of Invisible Pools

Do you notice any problem in the code below?

- (void)parseTitle:(NSString **)title note:(NSString **)note fromText:(NSString *)text {
    NSMutableArray *noteLines = [NSMutableArray array];
    [text enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) {
        if (*title == nil) {
            *title = line;
            return;
        }

        [noteLines addObject:line];
    }];
    if (noteLines.count > 0) {
        *note = [noteLines componentsJoinedByString:@"\n"];
    }
}

How about this?

- (void)parseTitle:(NSString * __autoreleasing *)title note:(NSString * __autoreleasing *)note fromText:(NSString *)text {
    NSMutableArray *noteLines = [NSMutableArray array];
    [text enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) {
        if (*title == nil) {
            *title = line;
            return;
        }

        [noteLines addObject:line];
    }];
    if (noteLines.count > 0) {
        *note = [noteLines componentsJoinedByString:@"\n"];
    }
}

How about this?

- (void)parseTitle:(NSString **)title note:(NSString **)note fromText:(NSString *)text {
    NSMutableArray *noteLines = [NSMutableArray array];
    [self autoreleaseFun:^(NSString *line) {
        *title = line;
        [noteLines addObject:line];
    }];
    if (noteLines.count > 0) {
        *note = [noteLines componentsJoinedByString:@"\n"];
    }
}

- (void)autoreleaseFun:(void (^)(NSString *line))block {
    @autoreleasepool {
        block([[NSDate date] description]);
    }
}

How about this?

- (void)parseTitle:(NSString **)title note:(NSString **)note fromText:(NSString *)text {
    NSMutableArray *noteLines = [NSMutableArray array];
    @autoreleasepool {
        *title = [[NSDate date] description];
        [noteLines addObject:line];
    }
    if (noteLines.count > 0) {
        *note = [noteLines componentsJoinedByString:@"\n"];
    }
}

If you still can't see the problem, the last hint I'll show you is my crash:

EXC_BAD_ACCESS(code=1, address=0x50000010)

#0  0x014cbde5 in objc_retain ()
#1  0x014cbe34 in objc_storeStrong ()
#2  0x00001ccb at the call of -parseTitle:note:fromText:

I reported this crash as a bug of ARC during iOS 7 beta. Recently an Apple engineer got back to me saying:

You are misunderstanding the __autoreleasing property that is auto added by the compiler to your NSString ** variables. Before returning from parseTitle:note:fromText: the value assigned to *line is released.

You need a local strong variable inside parseTitle:note:fromText: to hold onto the values passed into your block. Declare it with the __block attribute so its strong within the scope of the method.

In fact, I know these things very well — I know the first two pieces of code are the same and I know "the value assigned to *line is NOT released JUST BEFORE returning from parseTitle:note:fromText:". I just didn't see the source of the problem — I didn't visualize the last two pieces of code at the time. I replied:

I don't see at which point the autorelease pool drains before returning from parseTitle:note:fromText:. Can you explain that? Thanks a lot!

Though I never heard from him again, I kept thinking about it, until today. I finally decided to ask my peer programmers on Stackoverflow. The aha moment came just before the end of my writing of the question post:

The autorelease pool must drain somewhere, but where? At the end of each enumeration block execution?

The problem is exactly that I can't see the autorelease pool. I can't see it does not mean it doesn't exist. I don't create it does not mean someone else doesn't create it. The danger is in the invisible. The danger is in regions that are out of my control.

Finally, the correct code1:

- (void)parseTitle:(NSString **)title note:(NSString **)note fromText:(NSString *)text {
    NSMutableArray *noteLines = [NSMutableArray array];
    __block NSString *strongTitle;
    [text enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) {
        if (strongTitle == nil) {
            strongTitle = line;
            return;
        }

        [noteLines addObject:line];
    }];

    *title = strongTitle;
    if (noteLines.count > 0) {
        *note = [noteLines componentsJoinedByString:@"\n"];
    }
}

Memory management is hard not because it is hard to understand. It is hard because it is hard to make sure you don't make any mistake all the time, for it is so easy to overlook something somewhere.


  1. I've been using this correct code since I met the crash. That's before I reported the bogus bug. I know how to write the correct code. I just didn't know why the crashing one is wrong.