iosobjective-ccore-foundationtoll-free-bridging

Is a __bridge_transfer valid on a NULL object


Let's say a method returns a CFErrorRef via a pointer. This returned error may be NULL. So would it be safe to perform a __bridge_transfer still or should I check for NULL.

E.g.

CFErrorRef cfError;
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, &cfError);

NSError *error = (__bridge_transfer NSError *)cfError;

I don't see any mention of this in the documentation and CFRelease documentation specifically states This value must not be NULL. https://developer.apple.com/library/mac/documentation/CoreFoundation/Reference/CFTypeRef/Reference/reference.html#//apple_ref/c/func/CFRelease


Solution

  • The direct answer to the question is yes, you can use __bridge_transfer on NULL. But this isn't the right question.

    Read the documentation on ABAddressBookCreateWithOptions. In particular, check out the documentation for error:

    On error, contains error information. See “Address Book Errors.”

    This is important.

    1. error's value in the case of success is not documented.
    2. error being nil/NULL/0 (ever) is not documented.

    This isn't academic. Some APIs have historically set error to invalid values. Imagine the call set the CFError to -1. That's "valid" since the non-NULL reply means you're not supposed to interpret the error, but bridge casting -1 to a NSError will probably crash.

    That means you must not touch cfError unless an error is indicated by ABAddressBookCreateWithOptions returning NULL.

    CFErrorRef cfError;
    NSError *error;
    ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, &cfError); 
    if (addressBookRef == NULL) {
        error = (__bridge_transfer NSError *)cfError;
    }
    

    You didn't ask this, but one additional wrinkle here is that bridges aren't even required if the compiler recognizes that something is 0-equivalent. For instance, this code will compile silently (assuming _thing1 and _thing2 are instance variables):

    - (id)bar {
        if (_thing1) return NO;
        if (_thing2) return 0;
        return NULL;
    }
    

    This is sloppy code, and I you should not do this intentionally, but knowing it builds cleanly… it's a good thing to look for. I ran into a bug caused by something like this:

    - (NSNumber *)someCalculationWithError:(NSError *)error {
       return 0; // meant to return @(0)
    }