Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting a CFErrorRef to an NSError (or opposite) with ARC

I used to cast an NSError to CFErrorRef like this and using it in SMJobBless

NSError *error
BOOL removed = SMJobRemove(kSMDomainSystemLaunchd,
                               (CFStringRef) daemonBundleID,
                               auth,
                               true,
                               (CFErrorRef*) &error);
if (!removed) {
        NSLog(@"Failed to remove existing PacketTool");
        [NSApp presentError: error];
    }

As I've had errors with ARC, "Cast of an indirect pointer to an Obj-C pointer to 'CFErrorRef' is disallowed with ARC", I changed and decided to do the opposite

CFErrorRef *cfError = nil;
BOOL blessed = SMJobBless(kSMDomainSystemLaunchd, (__bridge CFStringRef)daemonBundleID,
                          auth,
                          cfError);
if (!blessed) {
    NSError *error = (__bridge NSError *)cfError;
    NSLog(@"Failed to bless PacketTool: %@", error);
    [NSApp presentError: error];
    return FALSE;
}

Now I've got an "Incompatible types casting 'CFErrorRef' to NSError *" with __bridge cast

What can I do?

Update: Thanks to Greg, correct code is now:

CFErrorRef cfError = nil;
BOOL blessed = SMJobBless(kSMDomainSystemLaunchd,
                          (__bridge CFStringRef) daemonBundleID,
                          auth,
                          &cfError);
if (!blessed) {
    NSError *error = (__bridge NSError *)cfError;
    NSLog(@"Failed to bless PacketTool: %@", error);
    [NSApp presentError: error];
    return FALSE;
}
like image 601
Tom Avatar asked Dec 06 '13 15:12

Tom


2 Answers

When you declare cfError you shouldn't use pointer *, you should use:

CFErrorRef cfError = nil;
NSError *error = (__bridge NSError *)cfError;

In the other way it works like that:

NSError *error = nil;
CFErrorRef ref = (__bridge CFErrorRef) error;

Hope this help.

like image 176
Greg Avatar answered Nov 15 '22 13:11

Greg


On Dec 7 '13 at 16:05, Tom added:

Update: Thanks to Greg, correct code is now:

CFErrorRef cfError = nil;
BOOL blessed = SMJobBless(kSMDomainSystemLaunchd,
                          (__bridge CFStringRef) daemonBundleID,
                          auth,
                          &cfError);
if (!blessed) {
    NSError *error = (__bridge NSError *)cfError;
    NSLog(@"Failed to bless PacketTool: %@", error);
    [NSApp presentError: error];
    return FALSE;
}

I know that this post is 2 years old but it is wrong and I don't want other programmers copying that wrong code. This code leaks memory as the CFError is never released!

CoreFoundation has no automatic memory management, also not when using ARC. ARC only applies to Obj-C objects. And CoreFoundation doesn't know autorelease or autorelease pools, so CoreFoundation objects (CFStringRef, CFNumberRef, CFErrorRef, etc.) you get from CoreFoundation functions are never autoreleasing. They either don't need to be released at all or it's up to you to release them. And in case of out errors (CFErrorRef *), it's up to you to release them.

See also https://stackoverflow.com/a/8628267/15809

The first part of the code is correct:

CFErrorRef cfError = nil;
BOOL blessed = SMJobBless(
    kSMDomainSystemLaunchd,
    (__bridge CFStringRef)daemonBundleID,
    auth, &cfError
);

But then you need to understand bridge casting. The simplest form or bridge casting is just __bridge and this cast tells ARC "don't do anything". If you do this

NSError * error = (__bridge NSError *)cfError;

You tell ARC: "Cast cfError to error but don't manage the memory of error after the cast, it's none of your business."

If you do this, you are still responsible for releasing the CFErrorRef! That means once you are done with cfError and with error ("and" as both point to the same object and if that is destroyed both pointers become invalid), you must do this:

CFRelease(cfError);

Otherwise you are leaking memory!

Alternatively you can tell ARC to manage the memory for you but then you need a different cast. If you cast like that

NSError * error = (__bridge_transfer NSError *)cfError;

you tell ARC: "Cast cfError to error and then its up to you to manage the memory of error."

Now you don't need to release anything since as soon as error goes out of scope, ARC will release it for you. And because error and cfError are in fact the same object, releasing error also releases cfError, so now you don't need to release anything. As the name implies, this cast "transfers" the object to ARC. Once that is done, you must not use cfError at all any longer as you cannot say for sure when exactly ARC will release error and as soon as it does, cfError is an invalid pointer, using it can easily crash your whole app.

Same holds true if you cast into the other direction. If you do

NSError * error = ...;
CFErrorRef cfError = (__bridge CFErrorRef)error;

ARC will still manage the memory of error, which is dangerous as, see above, when ARC decides it can destroy error, cfError will also become invalid. It is okay if you only use cfError in the current scope, but if your CFErrorRef needs survive regardless what ARC does, then you do this cast:

NSError * error = ...;
CFErrorRef cfError = (__bridge_retained CFErrorRef)error;

This tells ARC: "Retain error once, then cast error to cfError and don't ever balance this initial retain."

So even when error goes out of scope, it won't be released by ARC as the retain counter of the object won't become 0, it will still at least be 1 because of that cast. It's now up to you to take care of memory management and that means once you are done with cfError, you must release it:

CFRelease(cfError);
like image 33
Mecki Avatar answered Nov 15 '22 14:11

Mecki