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?
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. :)
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With