Блок освобождается, пока в NSDictionary (ARC) - PullRequest
14 голосов
/ 11 ноября 2011

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

Я подумал, что очевидным способом было добавить его в коллекцию 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 Ответ

31 голосов
/ 11 ноября 2011

Вопреки распространенному заблуждению, ARC не снимает автоматически блокировку блоков, передаваемых в качестве аргументов методам .Снятие стека происходит автоматически только при возврате блока из метода / функции.

Т.е. this ....

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

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

Это задокументировано здесь :

Как блоки работают в 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];

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

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

Тогда происходит нечто странное.По совпадению, я использовал блоки как значения в приложении на основе ARC на прошлой неделе, и он работает нормально.

У вас есть минимальный удобный пример?

...