4

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

Awesome-o
  • 2,002
  • 1
  • 26
  • 38
  • NULL isn't an object, it's a pointer value. NSNull is an iOS object that can be used to represent the NULL/nil value (much as NSNumber can represent an int). – Hot Licks Jul 12 '14 at 02:35
  • Yes but the object reference, `cfError`, is `NULL`. So my question holds. – Awesome-o Jul 14 '14 at 17:35

4 Answers4

3

You do not need to check for NULL.

ARC is a strictly compile-time mechanism. When you use __bridge_transfer you are merely transferring memory management responsibility of a variable to the compiler. Whether cfError happens to be NULL or not at runtime is completely irrelevant to the compiler.

In your case, ARC will insert a release for error, but if error happens to be nil it's a simple no-op.

Darren
  • 25,520
  • 5
  • 61
  • 71
  • 1
    You should not **touch** `cfError` unless `ABAddressBookCreateWithOptions` returns `nil`/`NULL`. Cocoa does not guarantee errors returned in this way to be `nil` *or* valid in this case. Read the docs for yourself: *On error, contains error information.* What's it contain on success? **It's not documented.** – Steven Fisher Jul 14 '14 at 18:11
  • Good call, that's certainly the safer and more appropriate way to do things. However, the method signature does guarantee that the passed in pointer points to a `CFError`. Therefore, semantically it should either return a `CFError` or `NULL` to the caller. Though that's certainly a risky game to play ;) – Awesome-o Jul 14 '14 at 22:29
2

The error will be non NULL if the return value of the function is NULL. The pattern for this kind of CF function is to wrap the error checking in an if statement. if (addressBookRef == NULL) { /* your error handling here */}

You should not try to bridge anything unless it is non NULL. Object ownership or more accurately retain count and responsibility for decrementing it, are not meaningful with NULL or nil. It would be an anti pattern. At best it's a null operation. Sending messages to nil with Objective-C is fine, including retain and release. It is not fine to pass a NULL value to CFRelease() or CGRetain()

uchuugaka
  • 12,679
  • 6
  • 37
  • 55
  • "Don't touch the error without checking the direct return value" is indeed the correct answer for this particular code, even though it doesn't directly answer the broader question. – jscs Jul 12 '14 at 18:42
  • Though this is the correct solution for this particular code, it still doesn't answer the question. – Awesome-o Jul 14 '14 at 17:36
  • I don't care about votes. I only do this to help others and exercise my brain. – uchuugaka Jul 14 '14 at 22:42
1

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)
}
Steven Fisher
  • 44,462
  • 20
  • 138
  • 192
  • What about the case of `ABAddressBookRequestAccessWithCompletion`? The completion handler has the parameters `(bool granted, CFErrorRef error)` and the documentation never explicitly states when a `CFErrorRef` will arrive. – Awesome-o Jul 15 '14 at 17:05
  • I'm also curious if you could provide some examples of APIs that "have historically set error to invalid values". I don't doubt it for a second from my exploration in other C languages, but I can't recall ever running into this issue in Objective-C. (Not that this is a good excuse either, really just curious.) – Awesome-o Jul 15 '14 at 17:09
  • Unfortunately, I'm not sure what the rules for completion handlers are. I imagine they're documented somewhere. My guess based on the API is that the error is valid if granted isn't `YES`, but that's just a guess. Unfortunately, I also don't remember which API set the error to something invalid. I was a new to Cocoa at the time; I just remember the engineer who answered saying it wasn't guaranteed that error wouldn't be set to something invalid, but that it *should* and the person reporting it should file a radar for it. That was back in days that Apple ran a mailing list. – Steven Fisher Jul 15 '14 at 20:54
0

Unlike NSObjects, sending messages to NULL CF objects is not ok. I don't know about bridging casts specifically, but I would guess that no, casting a CF object to an NSObject using __bridge_transfer is NOT ok.

Why not try it and see? Cast it to a variable in the local scope of an instance method. That way, as soon as the method goes out of scope the system should try to release the object.

Duncan C
  • 128,072
  • 22
  • 173
  • 272