Переменная Obj-C __block сохраняет поведение - PullRequest
8 голосов
/ 15 июня 2011

Я сталкиваюсь со странной проблемой при попытке получить доступ к переменной __block (изменяемый блок) из-за пределов блока, в котором она изменена. Это очень забавный пример, который я использую просто для лучшего понимания блоков в целом, но в настоящее время у меня есть контроллер с этим методом, который создает строку с содержимым NSDictionary, который использует NSDictionary enumerateKeysAndObjectsUsingBlock:

- (NSString*) contentsOfDictionary:(NSDictionary*)dictionary
{
    __block NSString *content = @"";

    [dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
        NSString* contentToAppend = [NSString stringWithFormat:@"Object:%@ for key:%@\n", obj, key];
        content = [content stringByAppendingString:contentToAppend];
        NSLog(@"Content in block:\n%@", content);
    }];

    NSLog(@"Content out of block:\n%@", content);

    return content;
}

Когда я запускаю этот метод со словарем, содержащим содержимое:

Value    Key
"Queen"  "card"
"Hearts" "suit"
"10"     "value"

Переменная content корректно изменяется внутри блока, и я получаю этот вывод при каждой итерации:

... Содержимое в блоке:

Object:Queen for key:card

... Содержимое в блоке:

Object:Queen for key:card
Object:Hearts for key:suit

... Содержимое в блоке:

Object:Queen for key:card
Object:Hearts for key:suit
Object:10 for key:value

Как только код выходит из блока, при доступе к строке content выдается EXC_BAD_ACCESS, и в одном случае запуска кажется, что он напечатал некоторую мусорную память (не может воспроизвести) ...

Что вызывает раннее освобождение этой переменной? У меня сложилось впечатление, что если дать ему определение __block, это будет означать, что оно сохраняется при использовании внутри блока и освобождается при выходе из блока. Но переменная сохраняется и автоматически высвобождается для запуска, поскольку является строковым литералом Я ожидаю, что он не будет освобожден до тех пор, пока этот метод не выйдет в ближайшее время.

Ответы [ 2 ]

15 голосов
/ 15 июня 2011

Это ваша проблема:

content = [content stringByAppendingString:contentToAppend];

-stringByAppendingString: возвращает новый, автоматически выпущенный объект.Адрес этого объекта хранится в content.Каждый проходит этот (неявный) цикл, то есть каждый вызов предоставленного блока, создает новый объект и затем присваивает адрес этого нового объекта content.Ни один из этих объектов не переживает содержащийся в нем пул авто-релизов.

Что вам нужно сделать, это использовать NSMutableString и напрямую добавить contentToAppend к изменяемой строке.Например:

- (NSString*) contentsOfDictionary:(NSDictionary*)dictionary
{
    NSMutableString *content = [NSMutableString string];
    [dictionary enumerateKeysAndObjectsUsingBlock:
    ^(id key, id obj, BOOL *stop){
        NSString* contentToAppend = [NSString stringWithFormat:
            @"Object:%@ for key:%@\n", obj, key];
        [content appendString:contentToAppend];

        NSLog(@"Content in block:\n%@", content);
    }];

    NSLog(@"Content out of block:\n%@", content);
    return content;
}

Обратите внимание, что __block больше не требуется, поскольку вы не назначаете content в любом месте блока.

5 голосов
/ 15 июня 2011

Внутренне, -enumerateKeysAndObjectsUsingBlock: использует пул автоматического выпуска.__block Объекты с областью действия не сохраняются после окончания срока жизни блока, поэтому вы в конечном итоге получаете объект, созданный в области видимости блока, который затем освобождается при сливе пула автоматического выпуска словаря, что происходит до того, как вы попытаетесь напечататьзначение content.

...