The Danger of Invisible Pools

Do you notice any problem in the code below?

- (void)parseTitle:(NSString **)title fromText:(NSString *)text {
    [text enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) {
        if ([line looksLikeTitle]) {
            *title = line;
            *stop = YES;
        }
    }];
}

How about this?

- (void)parseTitle:(NSString * __autoreleasing *)title fromText:(NSString *)text {
    [text enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) {
        if ([line looksLikeTitle]) {
            *title = line;
            *stop = YES;
        }
    }];
}

How about this?

- (void)parseTitle:(NSString * __autoreleasing *)title {
    [self autoreleaseFun:^(NSString *line) {
        *title = line;
    }];
}

- (void)autoreleaseFun:(void (^)(NSString *))block {
    @autoreleasepool {
        block(someString);
    }
}

How about this?

- (void)parseTitle:(NSString * __autoreleasing *)title {
    @autoreleasepool {
        *title = someString;
    }
}

What if I change the code from ARC to MRR?

- (void)parseTitle:(NSString **)title {
    @autoreleasepool {
        *title = [someString autorelease];
    }
}

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: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:fromText: the value assigned to title is released.

You need a local strong variable inside parseTitle: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 the first two pieces of code are the same. However, I don't believe the value assigned to *title is released JUST BEFORE returning from parseTitle:fromText:. I replied:

I don't see at which point the autorelease pool drains before returning from parseTitle: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 inside -[NSString enumerateLinesUsingBlock:]. 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 fromText:(NSString *)text {
    __block NSString *strongTitle;
    [text enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) {
        if ([line looksLikeTitle]) {
            strongTitle = line;
            *stop = YES;
        }
    }];

    *title = strongTitle;
}

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.