I have always known this pattern was the way to manage an NSError** parameter (specifically the BOOL return value and NSError* __autoreleasing * parameter:
-(BOOL)doSomethingWithString:(NSString*)string error:(NSError* __autoreleasing *)error {
if(![string length]) {
*error = [NSError blah blah];
return NO;
}
return YES;
}
To use this method followed this pattern:
NSError* error = nil;
if([self doSomethingWithString:#"" error:&error]) {
//great success!!
} else {
//log error
}
Now it seems that __autoreleasing is replaced with _Nullable?
I'm looking at this method from the official Apple Documentation: https://developer.apple.com/reference/foundation/nsfilemanager/1415371-createdirectoryaturl?language=objc
So, do I simply replace all of my __autoreleasing with _Nullable and all is good in this situation? If not, what is the new pattern for managing NSError** parameters?
No, these are 2 different things.
__autoreleasing specifies variable mode for ARC, so it can properly deduct how to read and write to it.
Nullability specifies... well whether parameter can be nil or not.
I think the confusion is caused by fact that Clang automatically assumes __autoreleasing for indirect parameters (http://clang.llvm.org/docs/AutomaticReferenceCounting.html#indirect-parameters), so some people just omit it. And if I remember correctly it wasn't always the case.
Otherwise you would end up with * __autoreleasing _Nullable *.
But it's not a bad thing to be explicit!
Related
This question already has answers here:
Cannot understand NSError/NSObject pointer passing behavior
(2 answers)
Why does NSError need double indirection? (pointer to a pointer)
(5 answers)
Closed 6 years ago.
The general use pattern for passing NSError is using pointer to pointer like this:
- (id)doSomethingWithObj:(NSObject *)obj error:(NSError *__autoreleasing *)error
{
NSLog(#"error parameter: %p", error);
id object = obj;
if (object != nil)
{
return object;
}
else
{
NSError *tmp = [NSError errorWithDomain:#"after" code:0 userInfo:#{#"after": #"boris"}];
*error = tmp;
return nil;
}
}
I know that since passing the address of the outside error pointer would allocate or replace the origin error, the above pattern would work.
But my issue about NSError or, more generic, Objective-C objects, is that, why I would get error if I wrote like this:
Instead of using NSError **, I tried NSError *, and what I was thinking is that, since now the parameter error is a type of NSError *, so *error should be the content that the pointer (error) was pointing to, then *error = [NSError errorWith...]; could replace the origin outside *error content.
But it didn't work like what I was thinking but popped up such above error.
So what confused me is that why *error = [NSError errorWith...]; does not work for the type of NSError * but work for NSError **.
When you pass a parameter into a method it is copied and later inside your method you deal with that copy:
- (void)changeA:(NSInteger)aToChange {
aToChange = 7;
}
Then, when you use that method you won't be able to change the value you passed from the inside of that method:
NSInteger a = 42;
[smb changeA:a];
// a is still 42
That's because inside -changeA: you deal with a copy of arguments, i.e. so you do something like (pseudo code):
// inside changeA:
NSInteger aToChange = a; // (value is copied)
aToChange = 7; // this does not modify a, right?
You might even get the address of that aToChange and try to do something with it:
// inside changeA:
NSInteger aToChange = a; // (value is copied)
NSInteger *pointerToCOPY = &aToChange; // get address of aToChange
NSInteger *pointerToA = &a; // get address of a
// here pointerToCOPY != pointerToA
So, going back to your code:
NSError *error = nil; // outer error, passed as a parameter
...
- (id)doSomethingWithObj:(NSObject *)obj error:(NSError *)error
{
// NSError *error(local copy) = error(passed argument);
error = tmp; // here error is the pointer to the local error copy
return nil;
}
As it was before with aToChange, you can't modify your external NSError error * from inside your method. That's why double pointers are used, you send pointer to your pointer, so that you still have access to the external error reference and can set it.
- (id)doSomethingWithObj:(NSObject *)obj error:(NSError **)error
{
// NSError **error(local copy) = error(passed argument);
*error = tmp; // Here *error is the pointer to the local error copy,
// When you dereference error you get NSError *, which you can set,
// that's your external error.
return nil;
}
Note than it's impossible to do this:
NSError *error = nil;
NSError *otherError = [NSError error with];
*error = otherError;
error is a pointer to a struct, *error is a struct, the one with single isa field (from the point of the id type defenition). It's inappropriate to assign pointer to error (otherError, i.e. platform pointer size int) to a struct.
And then, the last question remains, why can't you:
NSError *error = nil;
NSError *otherError = [NSError error with];
*error = *otherError; // why can't I assign to structs?
I'm not completely sure, but, maybe memory management and ABI comes in the game, here it could just copy bunch of bytes from one object to another as they are just a C structs.
As you know, Objective-C is a superset of C, and it is possible to assign structs there: Assign one struct to another in C. That's Objective-C peculiarity dealing with objects, I think. Everything is pretty much dynamic, you can create classes at the runtime and add instance variables (Mike Ash on that topic), so it is not known at the compile time what is beyond the isa field in the struct and, actually id is just a pointer to that struct with a single isa. That's not a good idea to just copy that single isa struct and say you're done, things will fall apart.
Under ARC, an out-parameter takes the following form (by default; this is equivalent to NSError **):
- (BOOL)tryWithError:(NSError *__autoreleasing *)err;
From the Transitioning to ARC Release Notes, if we pass the address of a __strong local variable, the compiler will create a temporary variable and generate the following code:
NSError *error; // strong
BOOL ok = [myObject tryWithError:&error];
// translated to
NSError *__strong error;
NSError *__autoreleasing tmp = error;
BOOL ok = [myObject tryWithError:&tmp];
error = tmp;
But if we do it with an instance variable:
#implementation Foo {
NSError *_error; // strong
}
- (void)bar
{
[myObject tryWithError:&_error];
}
...
this gives us the error
Passing address of non-local object to __autoreleasing parameter for write-back.
Why is this invalid? Couldn't the compiler just translate such code automatically to this?
- (void)bar
{
NSError *__autoreleasing tmp = _error;
[myObject tryWithError:&tmp];
_error = tmp;
}
After all, this is what I will be writing anyway to solve the problem!
Note: adding the out keyword to the parameter type will reduce the compiler's work slightly because it doesn't have to read the current value into the temporary variable — but this doesn't take care of the error.
A pointer to an ivar can't be passed to an “id __autoreleasing *” argument under ARC because that kind of pass-by-writeback is ill-formed. The respective section in the ARC specification lists legal forms of pass-by-writeback, the only one applicable here is
&var, where var is a scalar variable of automatic storage duration
with retainable object
, so only automatic storage duration (a local variable) is allowed.
Why this is invalid: I am pretty sure the reason here is compatibility with older code:
1) You should only look at the error writeback in the failure case. In the success case, there is no guarantee at all what's inside the error pointer.
2) In general, whether the writeback value should be used or not depends on the contract of the method. That is something the compiler cannot check.
This is the version of the code that matches the type of &error (NSError * __autoreleasing *) to the type of the writeback (NSError ** which is interpreted as NSError * __autoreleasing *). If ok is YES, the error value won't be touched.
NSError * __autoreleasing error;
BOOL OK = [myObject performOperationWithError:&error];
if (!OK) {
// use error
}
However, those __autoreleasing are ugly, so instead of forcing us to use __autoreleasing all over the place, the compiler allows us to pass a __strong (but local) variable as well (default ownership):
NSError *error;
BOOL OK = [myObject performOperationWithError:&error];
if (!OK) {
// use error
}
According to the docs, that gets rewritten to:
NSError * __strong error;
NSError * __autoreleasing tmp = error;
BOOL OK = [myObject performOperationWithError:&tmp];
error = tmp;
if (!OK) {
// use error
}
Not a problem at all, the error will only be used in the success case.
Now let's have a look at a __strong instance variable _error. Why doesn't the compiler allow that? Here is what the rewrite would look like:
NSError * __autoreleasing tmp = _error;
BOOL OK = [myObject performOperationWithError:&tmp];
_error = tmp;
if (!OK) {
// use error
}
The problem here is that the writeback in tmp would always be used (assigned to the instance variable _error), ignoring the contract of the method that the writeback should only be used in error cases (or in general whatever the documentation of the method says). A safe way to assign the last error to an instance variable would be
NSError * __autoreleasing tmp = _error;
BOOL OK = [myObject performOperationWithError:&tmp];
if (!OK) {
_error = tmp;
// use error
} else {
_error = nil; // Make sure that _error is nil if there was no error.
}
And that's only true for the convention of Cocoa's methods which return an error. In general there is no way for the compiler to tell what a method will do with an id *: There may be old methods out there that use different conventions. So if you really want to store the writeback in a __strong instance variable, you currently have to walk the extra mile yourself, and I don't expect this to change.
Simple question. What is the meaning of the double asterisk in the interface below? It's not an NSError pointer, but what?
- (BOOL)checkResourceIsReachableAndReturnError:(NSError **)error
It's a pointer to a pointer to an NSError. It's used as an "out parameter" -- or you may want to think of it as a pointer to an NSError instance, considering an NSObject instance is always a pointer.
You use it like this:
NSError * outError = nil; << reserve place and a name for the out parameter
// pass the error so the client may return it to you:
BOOL ret = [obj checkResourceIsReachableAndReturnError:&outError];
if (nil != outError) { << the error was set in checkResourceIsReachableAndReturnError:
NSLog(#"Error: %#", outError); << log it
}
…
On the implementation side, it looks like this:
- (BOOL)checkResourceIsReachableAndReturnError:(NSError**)outError
{
if (self.isThingReachable) {
// okay - the caller may not be interested in the error, test it:
if (0 != outError) {
// they are interested this time!
*outError = [NSError errorWithBlahBlahBlah];
}
return NO;
}
return YES;
}
You call it by sending a reference to an NSError*, like so:
NSError *myError = NULL;
[foo checkResourceIsReachableAndReturnError:&myError];
if (myError) {
/* look through info, handle error, etc. */
}
The -checkResourceIsReachableAndReturnError: method can modify the pointer myError, including allocating space for a new NSError. This allows you to easily check if there was an error, by simply checking if myError is non-NULL.
Check out this blog post: "Using NSError To Great Effect" and pay particular attention to the section "Passing Pointers to Pointers."
Also the wiki page on pointers has a section on "Multiple Indirection"
I have a function that returns an NSError object by reference:
NSData *foo(NSData *foo, NSError *__autoreleasing *outError);
This function uses an API that takes a pointer to storage for a CFErrorRef. I'd like to just pass outError to the underlying function directly, but I can't figure out the correct combination of declaration keywords and cast keywords to make clang agree with that plan. Is there one?
If you look at the clang notes for __autoreleasing it mentions that the magic happens at assignment time, which means that automatic casting can't help here. You need to actually make the assignment using a temporary variable as mentioned in the comments on the original post.
Try this:
NSError+CFErrorRef.h
#interface NSError (CFErrorRef)
- (CFErrorRef) cferror;
#end
NSError+CFErrorRef.m
#import "NSError+CFErrorRef.h"
#implementation NSError (CFErrorRef)
- (CFErrorRef) cferror
{
CFStringRef domain = (CFStringRef) self.domain;
CFDictionaryRef userInfo = (__bridge CFDictionaryRef) self.userInfo;
return CFErrorCreate(kCFAllocatorDefault, domain, self.code, userInfo);
}
#end
I wrote a quick little unit test to verify everything converted over and it appears to be working correctly. Then you can just perform the following selector on your NSError object
CFErrorRef e = [error cferror];
I'm reading a book on Core Data and at some point the author has this validation method:
- (BOOL)validateRadius:(id *)ioValue error:(NSError **)outError {
NSLog(#"Validating radius using custom method");
if ([*ioValue floatValue] < 7.0 || [*ioValue floatValue] > 10.0) {
// Fill out the error object
if (outError != NULL) {
NSString *msg = #"Radius must be between 7.0 and 10.0";
NSDictionary *dict = [NSDictionary dictionaryWithObject:msg forKey:NSLocalizedDescriptionKey];
NSError *error = [[[NSError alloc] initWithDomain:#"Shapes" code:10 userInfo: dict] autorelease];
*outError = error;
}
return NO;
}
return YES;
}
There are two things that confuse me and since I don't even know what they are technically called, can't seem to find in Google.
First one is the use of double asterisks ** in the method signature:
- (BOOL)validateRadius:(id *)ioValue error:(NSError **)outError {
The second is the use of a single asterisks * when on the reciever of a method call:
[*ioValue floatValue]
I haven't seen any of these two things before so I'm wondering what they are about. Just started iOS programming 6 or so months ago.
Any explanations or pointers to online documentation are very welcome.
(id *)ioValue means that ioValue is a pointer to an id, not an id itself. The expression *ioValue refers to the id that ioValue points to.
(NSError **)outError means that outError is a pointer to an NSError * (which is, in turn, a pointer to an NSError).
The usual reason for passing pointers to functions in this fashion is to allow the function to return something to the caller.
In the above case, the function could assign a new id to the variable that the caller passed in: *ioValue = something(). However, since the above function doesn't actually do this, it seems redundant; it could have been written as (id)ioValue, and then referred to it as ioValue instead of *ioValue.
The outError case makes perfect sense, however. In the event of an error, the function creates an error object (NSError *error = ...) and assigns it to the passed-in variable thus: *outError = error. This has the effect of changing the original variable that the caller passed in, so that when the function returns, the caller can inspect the variable to see the error that was produced:
id ioValue = something();
IOError *err;
if ([foo validateRadius:&ioValue error:&err]) {
NSLog("Yippee!");
} else {
reportError(err);
}