Bad Access Error when trying to pass error through functions
-
25-06-2021 - |
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?
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
(the second one is unfortunately in german :-))
Anyway, the basic concept is the following:
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.
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;
}
}