What's the right way to set an NSError outparam from inside an autoreleasepool block?
Asked Answered
A

1

9

I have a method like this:

- (void)processAThing:(id)thing error:(NSError * __autoreleasing *)error
{
    @autoreleasepool {

          // Start processing.

          // Find some partway through error..
          if (error) {
              *error = [NSError errorWithDomain...];
              return NO;
          }

          // More processing.
    }
}

This is broken and crashes, because the NSError is autoreleased, and when the return happens, the pool is drained, so the thing that the caller gets is now bogus.

I know I could significantly redesign the method so I collect all error cases outside the autorelease block, but I want to understand whether there's a correct way of handling the error object in this situation. I can't alloc/init a speculative NSError outside the pool block, because the domain and code properties are readonly (and I still think the reference would disappear when the method returns).

It solves the problem if I change the method declaration to this:

- (void)processAThing:(id)thing error:(NSError * __strong *)error

But then I need to fuss around at the call site in a nonstandard way, and this seems egregious to make the caller pay the price for my internal autoreleasepool.

Any thoughts? Thanks.

Appetizing answered 23/1, 2013 at 21:44 Comment(1)
Changing the signature to (NSError * __strong *)error actually seems like a reasonable alternative to me.Shepherd
R
8

I had this problem myself. In this case, I think you just need to declare a new strong reference just before the @autoreleasepool, and set the method argument just after the @autoreleasepool block from that temporary reference.

- (void)processAThing:(id)thing error:(NSError * __autoreleasing *)error {
  __strong NSError *errOut = nil;
  @autoreleasepool {
    // do your stuff and set errOut instead of error
  }
  if (error) {
    *error = errOut;
  }
}

(typed in browser, not error checked by compiler)

As for your premature return, I guess you'll have to use a jump label (even if it's not pretty).

Radloff answered 23/1, 2013 at 21:50 Comment(4)
you don't need to write __strong. It is that by defaultShanitashank
Yes, I put it there just to clarify.Radloff
Thanks Erik. Sounds like there's no other obvious pattern to use except minimizing the scope of the autorelease block and setting the outparam outside of it.Appetizing
BTW, if you need multiple returns (for example because of multiple error reasons), you can wrap your whole autoreleasepool in a @try block, and then do the *error = errOut dance in the @finally block (@catch is optional). This will ensure the out-error gets assigned properly before return in any case (thanks to my colleague Stephan for coming up with this).Loran

© 2022 - 2024 — McMap. All rights reserved.