Question

I am trying to implement some Errorhandling in my class. I don't think I have a full understanding of how Dealing with Errors works, but I noticed that people advised, to declare Errors in functions as (NSError *__autoreleasing *error) so I did that. I have problems passing the error through functions.

The following problem happens: breaking it down it seems like the following makes problems: (Sorry, the actual code is too long but I tried to extract hopefully the most important for the problem! I hope my scope of code contains the problem)

Imagine you have ObjectA with the method:

    -(NSString *) do1: (NSString *) withstuff error:(NSError *__autoreleasing *)error{
         //...
         //error happens
         *error = [[NSError alloc] initwithDomain: domain code: blah userinfo: infodict];
         return nil;
    }
    -(BOOL) do2error:(NSError *__autoreleasing *)error{
         NSString *doesntmatter = [self do1: @"whatever" error: error];
         if (doesntmatter == nil){
              return NO;
         }
    }

Now in another class (AppDelegate Object) I call the following:

       ObjectA* ob1 = [[ob1 alloc] init];
       NSError *errorBoom = nil;
       if ([ob1 do2error:&errorBoom] == NO){
            NSLog(@"error: %@",errorBoom); //---> bad access error 
       }

It seem like errorBoom is not accessible anymore? Is that because of "__autoreleasing", I tried to understand what it actually means but all explanations so far where not very useful for me since I am very new in this field... I hope you can help me on this!

EDIT:

Ok, I think I tracked down the error. Now I know what causes it but I don't know why exactly. I wrote an simple example application that extracts the main origin of the "Bad Access"-Error occurrence:

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions (NSDictionary *)launchOptions
    {
        self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
        // Override point for customization after application launch.
        NSError *errtest = nil;
        BOOL testBool = [self messaroundwitherr:&errtest];
        NSLog(@"Filled NSError? : %@",errtest); //<--Causes Bad Access.

        self.window.backgroundColor = [UIColor whiteColor];
        [self.window makeKeyAndVisible];
        return YES;
    } 
    -(BOOL) giveErrornow:(NSError *__autoreleasing *)err {
        NSMutableDictionary *errInfo = [[NSMutableDictionary alloc] init];
        [errInfo setObject:@"I feel like giving you an error!" forKey:NSLocalizedDescriptionKey];
        *err = [[NSError alloc] initWithDomain:@"nonesense"
                                  code:0
                              userInfo:errInfo];
        return  NO;
    }
    -(BOOL) messaroundwitherr:(NSError *__autoreleasing *)err{
        //@autoreleasepool { --> uncommenting that causes the error
            return [self giveErrornow: err];
        //}
    }

So apparently activating the autoreleasepool, makes the err variable be deallocated before even being able to read it from the outer function (AppFinishLaunching...). Now why is that happening exactly? I know that "@autoreleasepool" deallocates the variables after being used. In the original context I had a while loop, that's why I did it. Here it's just for comprehension reasons. So how does @"autoreleasepool" work? What happens actually with the command (*__autorelease *).

I guess I have to fully understand the notion of it before fixing it. Does NSError (*__autorelease *) err define a "pointer to pointer of an object that is going to be autoreleased (if yes, when) " for the ARC?

Was it helpful?

Solution

Ok I think I figured it out, with the help of two useful websites:

http://blog.pioneeringsoftware.co.uk/2012/03/06/out-parameters-when-arcing

and

http://openbook.galileocomputing.de/apps_entwickeln_fuer_iphone_und_ipad/apps_02_005.html#dodtpc7187bdd-6422-4c4f-92d2-c60983032cf5

(the second one is unfortunately in german :-))

Anyway, the basic concept is the following:

  1. Defining a variable by reference like:

        (NSError ** err) or (NSError *__autoreleasing * err) 
    

    always makes ARC rewriting it to:

        (NSError *__autoreleasing * err)
    

    and rewriting

         *err = [[NSError alloc] initWithDomain:@"nonesense"
                              code:0
                          userInfo:errInfo]
    

    to

         *err = .....]retain] autorelease];
    

    so the error object is marked as autorelease.

  2. Now as mentioned in the documentation:

    At the end of the autorelease pool block, objects that received an autorelease message within the block are sent a release message—an object receives a release message for each time it was sent an autorelease message within the block

So what basically happens from 1 and 2 is that, when uncommenting the @autoreleasepool block, the error is being created in the

    giveErrornow:(NSError *__autoreleasing *)err 

function, which is called from within the autoreleasepool, so *errtest is being released and deallocated before it can be used in

    NSLog(@"Filled NSError? : %@",errtest)

Yeah, so that's basically it. Seems to be a general problem when dealing with out Parameters and the problematic code can be changed for example like this:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions (NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    NSError *errtest = nil;
    BOOL testBool = [self messaroundwitherr:&errtest];
    NSLog(@"Filled NSError? : %@",errtest); //<--Causes Bad Access.

    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
} 
-(BOOL) giveErrornow:(NSError *__autoreleasing *)err {
    NSMutableDictionary *errInfo = [[NSMutableDictionary alloc] init];
    [errInfo setObject:@"I feel like giving you an error!" forKey:NSLocalizedDescriptionKey];
    *err = [[NSError alloc] initWithDomain:@"nonesense"
                              code:0
                          userInfo:errInfo];
    return  NO;
}
-(BOOL) messaroundwitherr:(NSError *__autoreleasing *)err{
    NSError *tempErr;
    BOOL retVal;
    @autoreleasepool {
        retVal = [self giveErrornow:&tempErr];
    }
    *err = tempErr;
    return retVal;
}

You are welcome to leave any comments about the problem, It would be great to be reassured that I understood the problem correctly.

OTHER TIPS

Finally, I am able to trap the reason and fixed that. In my case, NSError object assigned inside the autorelease block got released which results in the crash. I have created a copy of the error(NSError) object and assigned that outside block, it solves my problem.

- (void)processError:(NSError **) error {

    NSError *unzippingError = nil; // error value which will be assigned inside `@autoreleasepool` block
    NSError *err = nil; // it will hold the copy of error outside block

    @autoreleasepool {
        unzippingError = [NSError errorWithDomain:@"Domain" code:-1
                                         userInfo:@{NSLocalizedDescriptionKey:@"Error message"}];

        //copy the error in another variable to keep reference after @autoreleasepool block
        err = [unzippingError copy];
    }

    if (error) {
        * error =  err;
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top