ARC __bridge modifiers demystified

2019-01-16 08:30发布

问题:

I was asked recently by one of my friends about the new bridge modifiers that became active under ARC. He asked me if I knew which ones to use in specific times and what the difference between the different __bridge modifiers were. He asked me,"so how do they work, when do I use them, how do I use them , and how do they work "under the hood" "?

Note: This was supposed to be a "Share your knowledge" type of question, where I answered the question myself, but I'm not sure I set it up properly.

回答1:

Because I learned what they were and how they operated just recently, I want to share with anyone else who wishes to learn about the __bridge modifiers under ARC which can be a source of confusion due to the fact that toll-free bridging used to be accomplished with a simple cast.

It used to be that if you wanted to, say, cast your NSArray object to a CFArrayRef for any purpose, it could be done with a simple cast like so:

     NSArray* myArray = [NSArray alloc]initWithObjects:....]; //insert objects

     CFArrayRef arrayRef = (CFArrayRef) myArray;        

In this case, maybe you had an NSArray of colors and needed to cast it to CFArrayRef for using it with CoreGraphics to draw a gradient.

However with ARC, this will no longer work for you and you will receive this error:

Well what the heck does this mean!?!

Well it turns out, ARC doesn't like it when you do this kind of cast and will even give you a few "Fix it" solutions, which all seem to have that same __bridge keyword in them, so let's get right to them!

Under ARC we have 3 main __bridge modifiers:

__bridge

__bridge_retained ( which is partnered by the CFBridgingRetain function)

__bridge_transfer (which is partnered by the CFBridgingRelease function)

So we'll begin with __bridge. What is it? __bridge is just another way of saying: "Hey compiler, just give me my darn casted object!". The compiler will be happy to do so and return to you a casted object of your liking!

However, because you wanted a "freely" casted object like that, YOU are still responsible of releasing the memory for the originally allocated object. In this case, if I were to do this:

    NSArray* myArray = [NSArray alloc]init];
    CFArrayRef arrayRef = (__bridge CFArrayRef) myArray;

I am still responsible for releasing the myArray memory because it was the originally allocated object. Remember, __bridge just tells the compiler to perform the cast!! And because I am compiling under ARC, I don't explicitly have to call [-release] on the myArray object, ARC will do it for me!

Note, that the __bridge modifier works both ways! (Hence, toll-free bridging) and you can just as easily cast a CF object to an NS object the same way ( that is toll-free bridgeable!) like so:

CFArrayRef arrayRef; // allocate this arrayRef and give it a value later on
//... amazing code.....
NSArray* myArray = (__bridge NSArray*)arrayRef;

But since the CF object would be the originally allocated object, I must call CFRelease(whatever);

Now let's move on to __bridge_retained and its partner in crime CFBridgingRetain() . This __bridge modifier is geared EXPLICITLY towards transferring the ownership of an NS object TO A CF OBJECT (So we'll be expecting to manually CFRelease(whatever) this due to it being a CF type object)

Meaning, if I were to do my old scenario again, but this time with __bridge_retained:

 NSArray* myArray = [NSArray alloc]initWithObjects:....]; //insert objects

 CFArrayRef arrayRef = (__bridge_retained) myArray;

the arrayRef object now has explicit ownership of the memory that used to be owned by the myArray pointer. Because now the CF type has ownership, I must release it myself using CFRelease(whatever);

So what role does the CFBridgingRetain() function play in all this chaos? It plays the same exact role as doing the cast we just talked about! Let's take a look at the function prototype for CFBridgingRetain:

    CFTypeRef CFBridgingRetain(id x);

We can see, it pretty much just simplifies the whole (__bridge_retained) notion into one function! We're gettting back a CF object after "inputting" an NS type object! Radical! Yes, I know this is awesome! Too much coolness to take in one sitting! And yes, it also performs the memory "ownership" transfer.. how awesome!

And last, but by no means least, __bridge_transfer and the almighty CFBridgingRelease() !

__bridge_transfer works almost like the opposite of __bridge_retained. The __bridge_transfer modifier transfers the ownership of a CF object type to an NS object type.

So let's refer to the example that's been used throughout this to dissect it:

 NSArray* myArray = [NSArray alloc]initWithObjects:....]; //insert objects

 CFArrayRef arrayRef = (__bridge_retained) myArray;

 // at this point, arrayRef holds the ownership
 // Let's add this new line to change things up a bit:

 NSArray* otherArray = (__bridge_transfer NSArray*)arrayRef;

So what does this awesome little program that we just wrote exactly do?

Step 1: We allocated an NSArray

Step 2: We passed the ownsership of the array to the arrayRef object

// Before we continue to step 3, let's understand at this point arrayRef is the owner

Step 3: We re-transfer the ownership that USED to be owned by arrayRef back to an NSArray*

Because at this point, the otherArray pointer is the owner, it would seem sort of natural at this point to say [otherArray release] when we're done, right? Well this is where ARC kicks in and will take care of releasing that array for you!

And did you know it gets cooler? This __bridge modifier's awesome partner in crime: CFBridgingRelease()

makes it that much cooler! CFBridgingRelease has this function prototype:

   id CFBridgingRelease(CFTypeRef x);

And we see, this is exactly the same thing that happens when we cast with __bridge_transfer. And this function also transfers the ownership to the NS object! This is just fantastic!

Using the CFBridgingXXX functions maybe can make a little more sense at first, due to the fact that many objective-c programmers still have the notion of the NARC rule:

Everything that has been called with:

N ew

A lloc

R etain

C opy

must have a balancing -release call

So doing this:

     NSArray* myArray = [[NSArray alloc]init]; 
                                       // there's the A of NARC! 
                                       //(cleaned by ARC)
     CFArrayRef arrayRef = CFBridgingRetain(myArray); // there's the R of NARC!!
     //NSArray* other = CFBridgingRelease(arrayRef); // cleaned up by ARC

Can make the process of learning the __bridge casts easier due to the fact that the retain was matched with a release

If all this still may be confusing, think of it this way: You are a pointer to any NS object type. For the sake of consistency, let's say you're an NSArray pointer, that right now isn't pointing to anything. So you can sort of imagine that you, as the nil pointer, are standing in a bathroom with the lights turned off. ( The lights turned off represents that you aren't pointing to anything).

Then, later on in code, your programmer decides to assign you to a new NSArray. i.e, he/she says this:

      you = [[NSArray alloc]init];

Suddenly, the lights in the bathroom you were standing in, have turned on! You're pointing to an object! Now in normal program execution, when you're done using the object, you release it. So in this case, when you're done using the bathroom, you turn the lights off.

But the program you're in, unfortunately, isn't very "normal". The programmer decided to use some CoreFoundation objects! Bleh!

And he writes this line of code:

    CFArrayRef other = (__bridge_retained CFArrayRef) you;

So now what happens is that, another person walks into the bathroom at the same time you leave. Out of politeness, you don't turn the lights off because there's another person using the restroom and is responsible for turning the lights off when he/she leaves

In this case, because the new owner of the restroom is a CF object, the programmer must manually release it.

But what if he/she were to write this:

   CFArrayRef ref = (__bridge CFArrayRef) you;

what happens here is that, another person just barged into the same restroom as you without even asking! How rude! On top of that he expects you to clean up after him too! So you, being a gentlemen/lady turn off the lights when both of you finish.

However, since you are an NS type object, ARC comes and cleans it for you :)

And finally, what if the programmer writes this:

    you = (__bridge_transfer NSArray*)arrayRef;

What happens here is the exact opposite of the first scenario. Instead of you leaving the restroom at the same time as someone enters, you're the one who enters while the other person leaves

The same memory management rules apply. Since you took over "owning" the restroom, you must manually turn off the lights. And because you're an NS type object, ARC will do it for you... again :) Isn't ARC such a beauty!

I know this may seem a bit intimidating and confusing at first, but just work your way through it, read it again and you'll find out how incredible this ARC mechanism works!

Thanks everyone for reading! Hope this helped :)

Thanks to @rob mayoff and @ Krishnabhadra for all the extra help and suggestions!