Блок освобождается во время NSDictionary (ARC)

Я пытаюсь сохранить ссылку на блок, который был передан моему классу методом, для вызова позднее. Однако у меня возникают проблемы с поддержанием ссылки на него.

Очевидным способом, я думал, было добавить его в коллекцию ivar, и все они должны поддерживать сильные ссылки на их содержимое. Но когда я пытаюсь вытащить его обратно, это нуль.

Код довольно прост:

typedef void (^DataControllerCallback)(id rslt);

@interface DataController : NSObject {
    NSMutableArray* queue;
}
- (void) addBlock:(DataControllerCallback)callback;
- (void) functionToBeCalledLater;
@end

@implementation DataController

- (id) init {
    self = [super init];
    if (self != nil) {        
        queue = [NSMutableArray new];
    }
    return self;
}

- (void) addBlock:(DataControllerCallback)callback {
    NSDictionary* toAdd = [NSDictionary dictionaryWithObjectsAndKeys:
        [callback copy], @"callback",
        @"some other data", @"data", nil];
    [queue addObject:toAdd];
}

- (void) functionToBeCalledLater {
    NSDictionary* dict = [queue lastObject];
    NSLog(@"%@", [dict objectForKey:@"data"]; //works
    DataControllerCallback callback = [dict objectForKey:@"callback"]; //this is nil
    callback(@"an arguemnt"); //EXC_BAD_ACCESS
}

Что происходит?


Обновление: я пробовал его с [callback copy] и просто callback, вставляя в словарь, и не работает.


Обновление 2: Если я просто вставляю свой блок в NSMutableSet, пока я звоню copy, я в порядке. Он отлично работает. Но если это в NSDictionary, это не так.

Я действительно протестировал его, поставив точку останова сразу после создания NSDict, и обратный вызов никогда не вставлен. В описании четко указано "1 пара ключ-значение", а не два.

В настоящее время я обойду это со специальным классом, который просто действует как контейнер. Свойство callback объявляется как strong; Мне даже не нужно использовать copy.

Вопрос все еще стоит: почему это происходит? Почему в NSDictionary хранится блок? Имеет ли это отношение к тому, что я нацелен на iOS 4.3, и поэтому ARC должен быть встроен в статическую библиотеку?


Обновление 3: Дамы и господа: я идиот.

Код, представленный здесь, был явно упрощенной версией фактического кода; в частности, он оставлял некоторые пары ключ/значение из словаря.

Если вы сохраняете значение в NSDictionary с помощью [NSDictionary dictionaryWithObjectsAndKeys:], вам лучше быть уверенным, что одно из этих значений не nil.

Один из них был.

ICYMI, это привело к досрочному прекращению списка аргументов. У меня был аргумент userInfo-типа, передаваемый в один из методов "добавить в очередь", и вы, конечно, могли бы пройти "nil". Затем, когда я построил словарь, в этом аргументе заставило конструктор думать, что я завершил список аргументов. @"callback" было последним значением в конструкторе словаря и никогда не сохранялось.

Ответы

Ответ 1

Вопреки популярной ошибочной концепции ARC автоматически не деблокирует блоки, переданные в качестве аргументов методам. Он только автоматически деактивируется, когда блок возвращается из метода/функции.

т.е. это....

[dict setObject: ^{;} forKey: @"boom"];

... произойдет сбой, если dict выйдет за пределы области действия и вы попытаетесь использовать блок (на самом деле это не будет в этом случае, потому что это статический блок, но это подробная информация о компиляторе, t полагаться).

Это описанное здесь:

Как блоки работают в ARC?

Блоки "просто работают", когда вы передаете блоки вверх по стеку в режиме ARC, например как в возвращении. Вам больше не нужно вызывать Block Copy. Вы все равно необходимо использовать [^ {} copy] при передаче "вниз" стека в arrayWithObjects: и другие методы, которые сохраняют.

Поведение возвращаемого значения может быть автоматизировано, потому что всегда правильно возвращать блок на основе кучи (и всегда является ошибкой для возврата блока на основе стека). В случае блока-аргумента невозможно автоматизировать поведение таким образом, чтобы это было очень эффективным и всегда правильным.

Вероятно, анализатор должен был предупредить об этом использовании. Если это не так, укажите ошибку.

(Я взял стек, когда имел в виду кучу. Извините.)


Компилятор не автоматизирует блоки как параметры по нескольким причинам:

  • ненужное копирование блока в кучу может быть значительным снижением производительности
  • несколько копий блока могут значительно увеличить это ограничение производительности.

то есть:.

 doSomethingSynchronous(aBlock);
 doSomethingSynchronous(aBlock);
 doSomethingSynchronous(aBlock);
 doSomethingSynchronous(aBlock);

Если бы это означало четыре операции Block_copy(), и aBlock содержал значительное количество захваченного состояния, это было бы огромным потенциальным ударом.

• Есть только так много часов в день, и автоматизация обработки параметров изобилует неочевидными краевыми случаями. Если бы это было автоматически обработано в будущем, это можно было бы сделать без нарушения существующего кода, и, возможно, это будет сделано в будущем.

т.е. компилятор может сгенерировать:

 aBlock = [aBlock copy];
 doSomethingSynchronous(aBlock);
 doSomethingSynchronous(aBlock);
 doSomethingSynchronous(aBlock);
 doSomethingSynchronous(aBlock);
 [aBlock release];

Это не только устранит проблему с блоком-как-param, но также создаст только одну копию блока во всех потенциальных целях.


Вопрос все еще стоит: почему это происходит? Почему NSDictionary хранит блок? Имеет ли это какое-то отношение к факту что я настроен на iOS 4.3, и поэтому ARC должен быть встроен как статический библиотека?

Затем происходит что-то странное. Кстати, я использовал блоки как значения в приложении на основе ARC на прошлой неделе, и он работает нормально.

Есть ли у вас минимальный пример?