Установка NSError внутри блока с использованием ARC

Я хочу установить указатель NSError из блока в проекте, используя автоматический подсчет ссылок. Далее следует упрощенная версия моего кода:

- (BOOL)frobnicateReturningError:(NSError **)error
{
    NSArray *items = [NSArray arrayWithObjects:@"One", @"Two", @"Three", nil];

    __block Frobnicator *blockSelf = self;
    [items enumerateObjectsUsingBlock:^(id item, NSUInteger idx, BOOL *stop) {
        [blockSelf doSomethingWithItem:item error:error];
    }];
}

Это компилируется, но учитывая, что error может быть изменено doSomethingWithItem, я попытался создать локальный NSError для изменения блока, который затем будет использоваться для установки исходного error после перечисления (которое я не показал):

- (BOOL)frobnicateReturningError:(NSError **)error
{
    NSArray *items = [NSArray arrayWithObjects:@"One", @"Two", @"Three", nil];

    __block Frobnicator *blockSelf = self;
    __block NSError *blockError = nil;
    [items enumerateObjectsUsingBlock:^(id item, NSUInteger idx, BOOL *stop) {
        [blockSelf doSomethingWithItem:item error:&blockError];
    }];
}

Это не скомпилируется со следующей ошибкой:

передача адреса нелокального объекта в параметр __autoreleasing для обратной записи

Поиск этой ошибки в Google возвращает результаты только из самого исходного кода Clang.

Одно решение, которое кажется рабочим, но немного уродливым, состоит в том, чтобы иметь внутренний и внешний указатель ошибки:

- (BOOL)frobnicateReturningError:(NSError **)error
{
    NSArray *items = [NSArray arrayWithObjects:@"One", @"Two", @"Three", nil];

    __block Frobnicator *blockSelf = self;
    __block NSError *outerError = nil;
    [items enumerateObjectsUsingBlock:^(id item, NSUInteger idx, BOOL *stop) {
        NSError *innerError = nil;
        [blockSelf doSomethingWithItem:item error:&innerError];
        outerError = innerError;
    }];
}

Как правильно установить NSError внутри блока?


person Wes    schedule 02.08.2011    source источник
comment
Я предлагаю вам опубликовать на devforums.apple.com — некоторые люди Apple, скорее всего, ответят, чтобы помочь вам   -  person Mike Weller    schedule 04.08.2011
comment
@Майк Веллер: Почему? Я не вижу здесь ничего под NDA, ARC уже общедоступен.   -  person BoltClock    schedule 04.08.2011
comment
Я просто имею в виду, что многие люди из Apple на официальных форумах отвечают на вопросы ARC точно так же, как эти.   -  person Mike Weller    schedule 09.08.2011
comment
Я думаю, что теперь это исправлено, ошибка больше не возникает, поэтому нет необходимости во внутренней ошибке, достаточно просто пометить ошибку __block.   -  person malhal    schedule 06.12.2015


Ответы (2)


Примечание. С 2020 года этот обходной путь больше не нужен.


Попробуй это:

// ...
__block NSError *blockError = nil;
[items enumerateObjectsUsingBlock:^(id item, NSUInteger idx, BOOL *stop) {
    NSError *localError = nil;
    if (![blockSelf doSomethingWithItem:item error:&localError]) {
        blockError = localError;
    }
}];
// ...

Что касается того, почему именно это необходимо, я все еще пытаюсь понять это сам. Я обновлю этот ответ, когда сделаю это. :)

person jtbandes    schedule 02.08.2011
comment
По сути, когда вы создаете переменную __block, компилятор выдает код для создания копии этой переменной в куче во время выполнения, которая затем совместно используется вызывающей областью и блоком. Затем он перезаписывает все ссылки на эти переменные для доступа к этой памяти. Вот как переменные __block могут «пережить» свои первоначальные области видимости. Как только вы узнаете, что это происходит, легко понять, почему операции адресации во время компиляции с исходными переменными стека не повлияют на копию, выделенную в куче. Это немного упрощает его, но суть в том, почему это необходимо. - person ipmcc; 18.10.2011
comment
Локальная ошибка не нужна. Вы можете передать &blockError напрямую, если у него есть ключевое слово __block. - person Steve; 31.10.2012
comment
как сказал @Steve, нет необходимости создавать переменную localError. Просто передайте &blockError вместо &localError. На более высоком уровне проверьте наличие ошибок и выполните if(error) error = blockError; и вы сможете передать ошибку вызывающей функции! Честно говоря, с ARC и NSError* действительно сложно справиться. ARC — это огромный прогресс с точки зрения простоты по сравнению с MMM, но эта очень специфическая обработка NSError ** всегда была моей главной причиной сбоев в коде ARCd :-( Я действительно надеялся, что Apple могла бы упростить или задокументировать это более точно. - person Altimac; 28.11.2020

Как правильно установить NSError внутри блока?

Как видно из Что нового в LLVM? @ 14:55, два метода для решения проблемы с NSError, которая неявно автоматически освобождается.

Самое простое решение - использовать __strong

- (BOOL)frobnicateReturningError:(NSError *__strong *)error
{
    NSArray *items = [NSArray arrayWithObjects:@"One", @"Two", @"Three", nil];

    __block Frobnicator *blockSelf = self;

    [items enumerateObjectsUsingBlock:^(id item, NSUInteger idx, BOOL *stop) {
        NSError *innerError = nil;
        [blockSelf doSomethingWithItem:item error:&innerError];
        if(innerError && error) {
          *error = [NSError errorWithDomain:...];
        }
    }];
}

Второе исправление — использовать __block

- (BOOL)frobnicateReturningError:(NSError **)error
{
        NSArray *items = [NSArray arrayWithObjects:@"One", @"Two", @"Three", nil];
    
        __block Frobnicator *blockSelf = self;
        __block NSError *strongError = nil;

        [items enumerateObjectsUsingBlock:^(id item, NSUInteger idx, BOOL *stop) {
            NSError *innerError = nil;
            [blockSelf doSomethingWithItem:item error:&innerError];
            if(innerError) {
              strongError = [NSError errorWithDomain:...];
            }
        }];
        if (error) *error = strongError;
    }
person Alex Nolasco    schedule 06.10.2017
comment
Это предложение явно не похоже на Cocoa по двум основным причинам и не отвечает на заданный вопрос: 1/Cocoa ожидает проверки результата doSomethingWithItem:error: (который обычно должен возвращать BOOL или идентификатор), затем innerError ДОЛЖЕН быть заполнен ошибкой. 2/Ваша техника теряет то, что находится в innerError! вы создаете НОВУЮ ошибку, не получая никакой информации от innerError! - person Altimac; 28.11.2020