References
My guest for this first episode is Mikey Ward, a great teacher, presenter, and author and one of the nicest people you’d ever want to meet.
Mikey Ward is an instructor at Big Nerd Ranch, and you can find him on Twitter at @wookiee.
He was incredibly kind and patient to be my first guest in this new venture.
So Mikey, what programming concept are we talking about today?
(Mikey)
I think with your permission I’d like to start with something near to my heart, as it’s something I see both experienced and new programmers struggle with. Object references, and how we use and misuse them.
I suppose that could be an unfairly broad topic, since it really encompasses everything from value vs. reference semantics in Swift to strong reference cycle leaks, but it’s one where I think a little artful discussion could go a long way for folks.
(Daniel)
Great!
Oh wait “artful discussion” - nice.
So you and I are old enough that we remember Objective-C before ARC where he had to manage memory ourselves, in the snow, walking uphill back and forth to the compiler.
In Objective-C if we created a new object with ownership, it’s reference count would be 1.
Now if I’m interested in that object I would have to say how interested. Do I increase its reference count or not. Is my reference strong, weak, unsafe-unretained?
I’m not nostalgic for the old days where we did this work ourself with retain, release, and autorelease, but those who just joined us for Swift where the reference count is handled for us may be missing some of the underlying ideas.
(Mikey)
Exactly! And I think some of the newer Swift developers are leaning on those with lots of experience to help them understand what’s going on, but I feel like there’s a lot of advice out there grounded in the pre-ARC way of thinking.
I think updating our model for ARC’s and Swift’s optimizations is important.
Which leads us to the the trailhead, I suppose. Swift’s Value and Reference types.
There’s a lot that could be said about the stack and the heap but it’s easy to overthink, so let’s step back.
The question is, “when I declare a variable and put a value in it, where in memory does the value actually live?”
For value types such as Int
, that value lives inside the variable itself. If I pass a the variable into a function as an argument, the variable (and its contained value) is copied.
For reference types like UIViewController
, the variable you create doesn’t keep the entire object inside it. Instead, the object is created somewhere else in memory, and your variable instead only contains knowledge of where in memory the real object is. Specifically, the object’s memory address.
(Daniel)
I like that you’re thinking conceptually of the difference between value and reference types.
This split is clear in Objective-C and types like UIViewController
that is implemented in Objective-C that we may create and interact with in Swift.
But some of the Swift value types also end up using the heap as well. Our contract though is simple.
How’s this for an example. I walk in and you’re watching your AppleTV. You are controlling it with your iPhone. In other words, you hold a pointer to the AppleTV.
I sit down next to you and take out my iPhone and connect it to the AppleTV as well.
We each have a reference to the same tv. If you pause the program using your remote, the program I’m watching pauses as well. If I hit the play button then the program you’re watching starts playing again.
Our remotes are talking to the same AppleTV. We have references to the same memory location.
Now, I go to the kitchen and make a sandwich. You say, “that looks good, I’d like one too.”
This time it’s a physical object. I don’t want us connected to the same sandwich. We need our own instances.
I make you a copy of my sandwich and hand it to you.
You take a bite out of your sandwich.
It would creep me out if that meant a bite disappeared from my sandwich. The sandwich is a value type. We each have our own. Changes I make to mine don’t effect yours and vice versa.
(Mikey)
Exactly! Thinking about the stack and the heap is likely to get in the way for most people. What’s more important is thinking about whether my variable contains the entire value (a value type) or just a reference to it (a reference type).
Which simplifies the next step a bit: when I copy a variable of a value type, its entire contents are duplicated, as with “answer” and “fact” in my diagram, or the sandwiches in your example.
But with a reference type, like the Apple TV, copying the remote just means I have two ways to control it.
Copying a reference type variable only copies the reference. Which increases the number of references to the object in question.
So the happy path is that an object stays alive as long as there are references to it. If the reference count of an object reaches zero, the object self-destructs and gives its memory back to the system. I don’t want my AppleTV to explode if both remotes are unpaired from it, but here we are, those are the rules.
The Automatic Reference Counting done by the compiler, and the self-destruction of an object with a reference count of zero, means we rarely have to think about any of this.
But there are strange and complex ways in which these seemingly simple rules can get us in trouble.
(Daniel)
For example, what happens if we have a value type that contains a property that’s a reference type.
Going back to our AppleTV example, what if we don’t have iPhones with us - I know - that makes no sense, but work with me.
So you have a really cool 3D printer that you use to print a remote for the AppleTV.
I come into the room and see you watching television and using your remote and I ask you if I can have the remote.
You think, “no, this is like the sandwich” and you make a copy of the remote and hand it to me. So it’s a different physical remote but it has this reference type inside - this property that, in your remote, points to the AppleTV.
Will my remote point to the same AppleTV?
If we have a value type that contains a reference type, where do these things live?
(Mikey)
Your remote will absolutely point to the same AppleTV, and we’ll have a fun game of Menu Wars. When you copy a value type, you copy everything it contains. Remembering that the remote doesn’t contain the AppleTV itself (though that might be interesting to imagine), the remote only contains a reference to the AppleTV. When the remote is copied, any values and references it contains are copied. The objects at the other ends of those references are not.
And now we get to the nut of it. Experienced developers might be tempted to apply terminology like “stack types” and “heap types” to Swift, but I strongly encourage folks not to try to force the comparison, and to embrace Swift’s terms of art, “value type” and “reference type”.
Thinking about it strictly in those terms helps us to understand what’s happening under the hood of one of Swift’s more interesting design decisions.
Let’s draw a chain of conclusions from what we know so far:
Reference type variables are very small, because they don’t contain any real data, they only contain a reference to somewhere else in memory where the data (which may be a mix of other references and values) actually lives. This means that copying references and passing them into functions is inexpensive. Value type variables contain all of their data (which may include a mix of other values and references) This means that the computational and memory cost of copying and passing around value types is tied to the amount of data that they contain.
So here’s the big question: since several of Swift’s value types like Strings and Arrays can become very big, do we need to be careful about how many times we pass them around and copy them? Would it be better if Strings and Arrays were reference types?
(Daniel)
I agree that the right way to think about value types and reference types is their semantics, but to keep people from making assumptions about memory and performance it might be nice to spend a moment on stack and heap.
A WWDC talk from 2016 titled Understanding Swift Performance helped make this clear for me.
In Objective-C and C based language, primitives are created on the stack while objects are created on the heap and their references are on the stack. (To be pedantic, the exception is blocks which are reference types created on the stack that need to be copied to the heap if they’re going to stay around a while.)
Anyway, we explicitly do not have that clean separation in Swift. As you say, Strings and Arrays are value types and we should understand the contract that that implies. But we can’t reason that value types are kept on the stack and reference types on the heap.
The session showed that Strings are created on the stack but for even fairly small Strings, the characters - i.e. the contents of the String - are stored on the heap. This mixture of getting the contract for value types while the efficiency of the heap is explained in Ben Cohen’s portion of the 2019 WWDC video Modern Swift API Design and Johannes Weiss’ dotSwift talk High Performance systems in Swift
So, for me, the takeaway is that the library authors are aware of the costs of using value types while embracing the semantic benefits. It allows me to worry less about the performance and memory issues while adhering to your points on how we should think about value and reference types.
(Mikey) Right! The videos’ points on the under-the-hood heap storage and copy-on-write features of some of Swift’s standard library types are fun and enlightening, but in the end, are just implementation details. In the end, even though it might feel wasteful to pass a large string or array up and down the stack, the Swift authors have gone to great lengths to make it work fast and efficiently.
This really is a delightful language. Thank you for giving me the opportunity to chat with you about it!