Objective-C Блокирует с замыканием, которое ссылается на хост-объект - PullRequest
2 голосов
/ 19 мая 2011

Я играл с блоками и столкнулся со странным поведением. Это интерфейс / реализация, которая просто содержит блок с возможностью его выполнения:

@interface TestClass : NSObject {
#if NS_BLOCKS_AVAILABLE
    void (^blk)(void);
#endif
}
- (id)initWithBlock:(void (^)(void))block;
- (void)exec;
@end

@implementation TestClass
#if NS_BLOCKS_AVAILABLE
- (id)initWithBlock:(void (^)(void))block {
    if ((self = [super init])) {
        blk = Block_copy(block);
    }
    return self;
}
- (void)exec {
    if (blk) blk();
}
- (void)dealloc {
    Block_release(blk);
    [super dealloc];
}
#endif
@end

В то время как обычная реализация и передача обычного блока работает:

TestClass *test = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass");
}];
[test exec];
[test release];

Использование блока со ссылкой на объект, который создается, не:

TestClass *test1 = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass %@", test1);
}];
[test1 exec];
[test1 release];

Ошибка EXC_BAD_ACCESS, трассировка стека в Block_copy (блок); Отладчик в: 0x000023b2 <+0050> добавить $ 0x18,% esp

Я продолжал играть и перенес код выделения над инициализацией, это сработало:

TestClass *test2 = [TestClass alloc];
test2 = [test2 initWithBlock:^{
    NSLog(@"TestClass %@", test2);
}];
[test2 exec];
[test2 release];

И комбинирование обоих фрагментов тоже работает:

TestClass *test1 = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass %@", test1);
}];
[test1 exec];
[test1 release];

TestClass *test2 = [TestClass alloc];
test2 = [test2 initWithBlock:^{
    NSLog(@"TestClass %@", test2);
}];
[test2 exec];
[test2 release];

Что здесь происходит?

Ответы [ 2 ]

5 голосов
/ 19 мая 2011

В выражении присваивания значение r вычисляется перед присвоением значению l.

Это означает, что в:

TestClass *test1 = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass %@", test1);
}];

выполняется следующая последовательность операций. Редактировать: , как указал Джонатан Гринспан, нет определенного порядка для шагов 1 и 2, поэтому может быть так, что шаг 2 будет выполнен перед шагом 1.

  1. Отправить +alloc до TestClass
  2. Создать блок, который ссылается на test1, который еще не был инициализирован.test1 содержит произвольный адрес памяти.
  3. Отправка -initWithBlock: объекту, созданному на шаге 1.
  4. Присвойте значение r test1.

Обратите внимание, что test1 указывает на действительный объект только после шага 4.

In:

TestClass *test2 = [TestClass alloc];
test2 = [test2 initWithBlock:^{
    NSLog(@"TestClass %@", test2);
}];
[test2 exec];
[test2 release];

последовательность:

  1. Отправка +alloc вTestClass
  2. Назначьте значение для test2, которое теперь указывает на объект TestClass.
  3. Создайте блок, который ссылается на test2, который указывает на TestClassобъект на шаге 2.
  4. Отправьте -initWithBlock: на test2, который был правильно назначен на шаге 2.
  5. Назначьте значение для test2.
1 голос
/ 17 сентября 2011

Проблема заключается в том, что при создании блока он будет копировать (делать отдельную копию) любые не __block переменные, которые он захватывает. Поскольку test1 неинициализируется во время создания вашего блока, вы будете использовать неинициализированный указатель для test1 при запуске блока.

Правильное решение - объявить test1 с __block. Таким образом, состояние разделяется между блоком и охватывающей областью, и после того, как test1 назначено в охватывающей области, блок может получить доступ к измененному значению:

__block TestClass *test1;
test1 = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass %@", test1);
}];
[test1 exec];
[test1 release];

p.s. Третий пример (выполнение alloc до и затем присвоение результата init) ненадежен, поскольку в общем случае метод init объекта не гарантирует возвращение объекта, для которого он вызван (init разрешено освобождать например, и возвращает nil, если это не удалось, например).


Обновление: Приведенный выше код предназначен только для MRC, поскольку __block переменные не сохраняются в блоке.

Но в ARC приведенный выше код вызовет цикл сохранения, поскольку __block переменные указателя объекта по умолчанию сохраняются блоком. В ARC правильный код:

TestClass *test1;
__block __weak TestClass *weakTest1;
weakTest1 = test1 = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass %@", weakTest1);
}];
[test1 exec];
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...