iOS: свойство Block напрямую устанавливает сбои при доступе - PullRequest
9 голосов
/ 11 января 2012

Рассмотрим следующий код:

@interface ClassA : NSObject
@property (nonatomic, copy) void(^blockCopy)();
@end

@implementation ClassA

@synthesize blockCopy;

- (void)giveBlock:(void(^)())inBlock {
    blockCopy = inBlock;
}

@end

Затем используйте его в классе, который имеет свойство strong типа ClassA с именем someA:

self.someA = [[ClassA alloc] init];
[self.someA giveBlock:^{
    NSLog(@"self = %@", self);
}];
dispatch_async(dispatch_get_main_queue(), ^{
    self.someA.blockCopy();
    self.someA = nil;
});

ЕслиЯ запускаю тот встроенный O3 с включенным ARC, на iOS он вылетает во время вызова self.someA.blockCopy(); внутри objc_retain.Почему?

Теперь я понимаю, что люди, вероятно, скажут, что я должен установить это с self.blockCopy = inBlock, но я действительно думал, что ARC должна делать правильные вещи здесь.Если я посмотрю на сборку (ARMv7), созданную методом giveBlock:, то она будет выглядеть следующим образом:

        .align  2
        .code   16
        .thumb_func     "-[ClassA giveBlock:]"
"-[ClassA giveBlock:]":
        push    {r7, lr}
        movw    r1, :lower16:(_OBJC_IVAR_$_ClassA.blockCopy-(LPC0_0+4))
        mov     r7, sp
        movt    r1, :upper16:(_OBJC_IVAR_$_ClassA.blockCopy-(LPC0_0+4))
LPC0_0:
        add     r1, pc
        ldr     r1, [r1]
        add     r0, r1
        mov     r1, r2
        blx     _objc_storeStrong
        pop     {r7, pc}

Это вызов objc_storeStrong, который в свою очередь выполняет retain в блоке и release на старом блоке.Я предполагаю, что ARC не замечает должным образом, что это свойство блока, так как я думаю, что оно должно вызывать objc_retainBlock вместо обычного objc_retain.

Или я просто совершенно не прав, и ARC делает то, что делаетэто документы, и я только что прочитал это неправильно?

Обсуждение очень приветствуется по этому поводу - я нахожу это довольно интригующим.

Примечания:

  • Он не падает на OS X.
  • Он не падает, встроенный O0.

1 Ответ

12 голосов
/ 11 января 2012
- (void)giveBlock:(void(^)())inBlock {
    blockCopy = inBlock;
}

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

То, что он не падает в определенных средахпросто случайно;он не потерпит крах, пока стековая версия блока не будет перезаписана.Безусловным признаком этого является случай, когда происходит сбой с отключенной оптимизацией.При отключенной оптимизации компилятор не будет повторно использовать стековую память в любой заданной области, в результате чего память будет «действительной» в течение длительного времени после того, как она должна быть.

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

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

Т.е. если бы у вас былочто-то вроде:

BlockType b = ^(...) { ... capture lots of gunk ... };
SomeRandomFunc(b);

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

...