12

I wish to set an NSError pointer from within a block in a project using automatic reference counting. What follows is a simplified version of my code:

- (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];
    }];
}

This compiles but given error may be modified by doSomethingWithItem I tried creating a local NSError for the block to modify, which would then be used to set the original error after the enumeration (which I haven't shown):

- (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];
    }];
}

This fails to compile with the following error:

passing address of non-local object to __autoreleasing parameter for write-back

Googling for this error only returns results from the Clang source code itself.

One solution that seems to work but is a bit ugly is to have an inner and outer error pointer:

- (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;
    }];
}

What is the correct way to set an NSError from within a block?

4
  • I suggest you post on devforums.apple.com - some Apple folk will most likely respond to help you Commented Aug 4, 2011 at 11:52
  • @Mike Weller: Why? I don't see anything under NDA here, ARC is already public. Commented Aug 4, 2011 at 17:14
  • I just mean lots of Apple folk are on the official forums answering ARC questions just like these. Commented Aug 9, 2011 at 12:18
  • 1
    I think this is fixed now, the error no longer occurs so no need for an inner error, just marking the error __block is enough. Commented Dec 6, 2015 at 7:52

2 Answers 2

9

Note: As of 2020, this workaround is no longer necessary.


Try this:

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

As for exactly why this is necessary, I'm still trying to get a grasp on that myself. I'll update this answer when I do. :)

Sign up to request clarification or add additional context in comments.

3 Comments

In essence, when you create a __block variable, the compiler emits code to make a heap-allocated copy of that variable at runtime, which is then shared between the calling scope and the block. It then rewrites all references to those variables to access that memory instead. That's how __block variables can 'outlive' their original scopes. Once you know that's hapenning, it's easy to see why compile-time address-of operations against the original stack variables are not going to affect the heap-allocated copy. That's slightly over-simplifying it, but is the crux of why this is necessary.
The localError isn't necessary. You can pass &blockError directly as long as it has the __block keyword.
as @Steve said, there is no need to create a localError variable. Simply pass &blockError instead of &localError. At the higher level check for failure and do if(error) error = blockError; and you'll be able to pass the error to the calling function! To be honest, ARC and NSError* are really hard to handle. ARC is a huge progress in terms of easyness compared to MMM, but this very specific NSError** handling has always been my number one crasher in ARCd code :-( I really hoped Apple could have simplified or documented that more precisely.
2

What is the correct way to set an NSError from within a block?

As seen on "What's new in LLVM?" @ 14:55, there are two techniques to address the issue with the NSError which is implicitly autoreleasing.

Easiest fix is to use __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:...];
        }
    }];
}

Second fix is to use __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;
    }

1 Comment

This proposal is clearly not Cocoa like for 2 major reasons and is not responding to the asked question : 1/ Cocoa expects to test the result of doSomethingWithItem:error: (which should typically return a BOOL or an id), then innerError MUST be filled with an error. 2/ Your technique loose what's in innerError! you're creating a NEW error without getting any information from innerError!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.