Developer Tools • iOS, macOS, watchOS • 39:43
Swift supports rich first-class value types in the form of powerful structs, which provide new ways to architect your apps. Learn about the differences between reference and value types, how value types help you elegantly solve common problems around mutability and thread safety, and discover how Swift's unique capabilities might change the way you think about abstraction.
Speakers: Doug Gregor, Bill Dudney
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
[Doug Gregor]
Thank you. Hello. I'm Doug Gregor. I'm here with my colleague Bill Dudney, and we will talk about building better apps with value types in Swift. First, we are going to start off talking about reference semantics, and then we will delve through immutability as a solution to some of the problems that reference semantics pose. Dive into value semantics and value types, how it works, especially how it works in Swift, and then talk about using value types in practice and then the mixing of reference types and value types together within Swift. Let's get started.
Reference semantics. So the way into reference semantics in the Swift world is to define a class. Here's a very simple temperature class. We are storing our temperature value in Celsius. We want to have this nice computed Fahrenheit property so we can always get to it with the correct unit. Simple, abstracted version of a temperature. Let's try to use it in some simple code. We create a home instance, we create a temperature instance. We set our thermostat to a balmy 75 degrees Fahrenheit.
Now we decide, oh, it's getting close to dinnertime and I want to bake some salmon. So I set my oven to 425 degrees and hit Bake. Walk away. Why is it so hot in here? What's going on? You know what happened. We hit this case of unintended sharing. Think of the object graph.
We have our house. It has a thermostat and an oven. We have this temperature object that temp points to. When we set our thermostat, we wired it to the same object as the temp. Things look like fine until we go ahead and do a mutation, and now this unintended or unexpected sharing just caused us to set our thermostat to 425 degrees Fahrenheit.
At this point it was already game over, but just for good measure let's wire our thermostat to our oven because we've lost already. So what did we do wrong? In this world where you have reference semantics, you want to prevent sharing, and so you do copies. Okay? Let's do this the right way.
Okay. I'm going to set my temperature to 75 degrees Fahrenheit again. When I set the temperature of my thermostat, I will cause a copy. So I get a brand new object. That's what my thermostat's temperature points to. When I go ahead and change the temperature of my temp variable, it doesn't affect it. That's good. Now I go and set the oven's temperature, I will make another copy.
Now, technically, I don't need this last copy. It's inefficient to go waste time allocating memory on the heap to create this extra copy. But I'm going to be safe because the last time I missed a copy, I got burned [groaning]! Come on, it's a Friday session, give me a break!
[ Applause ]
So we refer to this as defensive copying, where we copy not because we know we need it, but because if we do happen to need it now or sometime down the road, it's really hard to debug these problems. And so it's way too easy to forget a dot copy any time we assigned a temperature somewhere in our oven. So instead, I will bake this behavior right into my oven [laughter]. All right, I'm done. I'm done. I'm sorry. I'm sorry [applause].
Naturally I have to do this exact same thing for the thermostat, all right? So now we've got a whole bunch of boilerplate we are copying and pasting, and sooner or later, you will be knocking on my door asking for a language feature here. Hold off on that, and let's talk about where we see copying in Cocoa and Objective-C. So in Cocoa, you have this notion of the NSCopying protocol. And this codifies what it means to copy, and you have a whole lot of data types and this NSstring, NSArray, NSURLRequest, etcetera. These things conform to NSCopying because you have to copy them to be safe.
We are in a system that needs copying, and so you see a lot of defensive copying for very, very good reasons. So NSDictionary will defensively copy the keys you place in the dictionary. Why? Well, if you were to hand NSDictionary a key to insert it and then go change it in a way that, say, alters the hash value of it, you will break all the internal variance of NSDictionary and blame us for your bugs. We don't really want that. What we really do is we do defensive copying in NSDictionary. That's the right answer in this system, but it's unfortunate that we are losing performance because we are doing these extra copies.
Of course, we moved this all the way down into the level of the language with copy properties in Objective-C that do defensive copying on every single assignment to try to prevent these problems, and it helps, all this defensive copying helps, but it's never good enough. You still have a ton of bugs. So we have problems with those reference semantics and there's mutation.
Maybe the problem here is not the reference semantics but the mutation itself. Perhaps we should move to a world of immutable data structures with reference semantics. If you go talk to someone in the functional programming community, they will say, yeah, we have been doing this for decades! And it does improve things there. So you can't have unintended side effects in a world where there are no side effects, and so immutable reference semantics are a consistent way to work in the system. It doesn't have these unintended consequences, which we ran into in our little temperature example.
The problem is that immutability has some disadvantages. It can lead to some awkward interfaces, and part of this is just the way the languages work, and part of this is that we're used to living in a world where we can mutate things, and we think about state and mutating state, and so thinking in purely a immutable world can be a little bit weird for us at times. There's also the question of an efficient mapping down to the machine model. Eventually, you have to get down to machine code, and you are running on a CPU that has stateful registers, and stateful caches, and stateful memory, and stateful storage.
It's not always easy to map an immutable algorithm down to that level efficiently. Let's talk through a couple of these. We will take this temperature class of ours and try to make it safer by making it immutable. So, all we had to do here was change the stored property Celsius from a var to a let, and now you can't change it. And then the Fahrenheit computer property loses its setter. So you can't change the state of temperature no matter what you do. We add some initializers for convenience.
Okay. Let's talk about awkwarding. Awkward immutable interfaces. Before, if I wanted to, say, tweak the temperature of my oven by 10 degrees Fahrenheit, this was a simple operation. Plus equals 10. That does it. That's it. How would we do this if we have taken away mutation? Well, we have to go and grab the temperature object in the oven, create yet another temperature object that has the new value. All right? So it's a little bit more awkward. There's more code, and we wasted time allocating another object on the heap.
But in truth, we have not embraced immutability here because we did an assignment that mutated the oven itself. If we were embracing the notion of immutable reference types throughout here, we would be creating a new temperature to put in a new oven to put in a new home [laughter].
Awkward! So let's get a little bit more theoretical and do some math. So the Sieve of Eratosthenes is an ancient algorithm for computing prime numbers. It uses mutation and actually lends itself well to drawing things out in the dirt with a stick. So this is the implementation of the mutating version in Swift.
We are going to walk through it so you get the idea behind it. First thing you do: create an array. Notice the var because we are going to mutate this array. Notice this array is from two, first prime number, up through whatever number you want to compute. We will do 20 here.
Now, the outer loop, each time through it, we will pick the next number in the array. That number is a prime number, P. What the inner loop is going to do is walk over all of the multiples of P, erasing them from the array by setting them to zero.
Because, of course, if you are a multiple of a prime number, you are not prime. Back to the outer loop, we go and grab the next number, it's a prime number. We erase all of its multiples from the array. So it's a very, very simple algorithm. Think of the stick in the dirt. You are just scratching things out.
Once we are done going through all of our iterations, we go down here. And we do the last simple operation, which says, everything that we have not zeroed out in the array, that's part of our result. So we will do that with a filter. Simple algorithm, entirely based on mutation.
Now that doesn't mean you can't express it in a world without mutation. You can. Of course. So to do that, we are going to use Haskell, because it's a pure functional language [applause]. Yes, I knew people would love it! All right. So this is the Haskell formulation. It's -- if you read Haskell, it's beautiful.
It's functional. It doesn't mutate at all. Here's a very similar implementation because it turns out Swift can do functional also, and if you want to make it lazy, that's an exercise to the reader but it's not that much harder. And we're going to walk through how this algorithm works, because It's very, very similar.
We start with our array of numbers from 2 to 20. In the simple basis case, if there's no numbers, well, there's no prime numbers in there. So that's the first If statement. It's trivial. Otherwise, what you do is we take out the first number, that's always going to be prime.
And separate it from the remaining numbers. Right? Haskell did this with pattern matching, and we can do slicing here. Then we take that prime number and we run a filter operation over all of the elements here in this remaining array. Copying only those things that aren't multiples of that prime number.
Now we recurse and do it again. Split out the three and this is our new prime number. Go ahead and run the filter. Eliminate all the multiples of three, and so on and so forth. What happens here is you end up building along this left-hand diagonal the actual prime numbers, and as a result they all get concatenated together.
The idea is similar. It's very, very similar. But it's not the same algorithm because it has different performance characteristics. So this result comes from a brilliant paper by Melissa O'Neil called "The Genuine Sieve of Eratosthenes," where she showed the Haskell community that their beloved sieve was not the real sieve because it did not perform the same as the real sieve.
She goes through much more complicated implementations in Haskell, that can get back to the performance characteristics. Read the paper and check it out. It's really cool. I want to give you a taste of why this is the case. Look at either the Haskell list comprehension or the equivalent Swift filter below.
In this nonmutating version, this operation will walk over every single element in the array and do a division operation to see if it should still be in the next step, to see if it's a multiple of P or not. In the original mutating algorithm, we only walked over the multiples of the prime number and those, of course, become increasingly sparse as you get to bigger and bigger numbers.
So you are visiting fewer elements, and moreover, you only have to do an addition to the get to the next element. So you are doing less work per element. That matters. And the nonmutating version is not as efficient as the mutating version without a whole ton of work.
Let's bring it back to Cocoa. So you see uses of immutability in the Cocoa, Cocoa Touch frameworks. There's a lot of them. And that's Date, UI image, NSNumber, and so on. These are immutable types, and having these immutable types improves safety. It's a good thing because you don't have to worrying about copying. You don't have to worry about your sharing having unintended side effects.
But you also see the downsides there when you work with it. I gave myself a little task in Objective-C. I want to create an NSURL by starting with my home directory and adding successive path components to get to some directory. And I wanted to do it without the mutation in the reference semantic world. So I created an NSURL. Each time through the loop, I create a new URL by appending the next path component.
This is not a great algorithm, really. Every time through, I'm creating an NSURL, another object, the old one goes away, and then the NSURL is going to copy all of the string data each time through the loop. Not very efficient there. Doug, you are holding it wrong. Really you should be collecting all these components into an NSArray and then use file URL with path components.
Fine. But, remember, we are embracing immutability here. So when I create my array, I will create an NSArray with a particular object, all right, that's the home directory. Each time through, I create a new array, adding one more object. I'm still quadratic. I'm still copying the elements. I'm not copying the string data. So it's a little better. I'm still copying the elements.
This is why we don't fully embrace immutability in the world of Cocoa because it doesn't make sense. Instead, you use mutability in more localized places where it makes sense. Collect all of your components into an NSMutable array. Then use file URL with path components to get back to that immutable NSURL.
So immutability is a good thing. It makes the reference semantic world easier to reason about. But you can't go completely to immutability or you start to go crazy. So that brings us to value semantics. With value semantics, we take a different approach. We like mutation. We think it's valuable. We think it's easy to use when done correctly. The problem, as we see it, is the sharing.
So you already know how value semantics work and you demand on it all the time, whether you are in Objective-C or in Swift. The idea is simple: if you have two variables, the values in those variables are logically distinct. So I have an integer A, I copy it over to an integer B. Of course they are equivalent; it's a copy. I go to mutate B. If I told you that would change A, you would say I'm crazy.
These are integers. They have value semantics in every language we have ever worked with. Go to CGPoint, for example. Copy from A to B, mutate B, it's not going to have any effect on A. You are used to this. If CGPoint didn't behave this way, you would be really, really surprised.
The idea of value semantics is take this thing we already know and understand for the very fundamental types, like numbers and small structs containing numbers, and extend it outward to work with much, much richer types. So in Swift, strings are value types. You create A, copy from B to A, go ahead and change B in some way, it won't have any effect on A. Because it's a value type. A and B are different variables. Therefore, they are logically distinct.
Okay? Why shouldn't the arrays behave exactly the same way? Create A, copy it over to B, mutate B. It has no effect on A. They are completely distinct values. Last one, well, dictionaries, of course. It's just a collection. You put value semantic things into it, you get value semantics back. The great thing here is that value types compose beautifully. So you can build up very rich abstractions all in the world of value semantics easily.
So in Swift, all the fundamental types -- integers, doubles, strings, characters, et cetera -- they are all value types. They have this fundamental behavior, the two variables are logically distinct. All the collections we build on top of them -- array, set, dictionary -- are value types when they're given value types.
And the language abstractions you use to build your own types -- tuples, structs, and enums -- when you put value types into those, you get value types out. Again, it's very, very easy to build rich abstractions all in the world of value semantics. Now, there's one more critical piece to a value type, and that is that you have the notion of when two values, two variables of value type, are equivalent. They hold the same value. And the important thing is that identity is not what matters.
Because you can have any number of copies. What matters is the actual value that's stored there. It doesn't matter how you got to that value. I will tell you things that are really, really silly. Here we have A, we set it to 5, and we have B and we set it to 2 plus 3. Of course A and B are equivalent.
You work with this all the time. You couldn't understand integers if they didn't work this way. So just extend that notion out. Of course it's the same thing for CGPoints, you wouldn't be able to understand them if it wasn't this way. Why shouldn't strings behave exactly the same way? It doesn't matter how I get to the string "Hello, WWDC." The string is the value, and the equality operator needs to represent that. You can make this arbitrarily crazy and stupid. So here I will go and do some sorting operation. What it comes down to though is that I have two arrays of integers, the integers have the same values. Therefore, these things are equivalent.
When you're building a value type, it's extremely important that it conform to the Equatable protocol. Because every value type out there should be equatable. That means it has the equal equal operator to do a comparison, but that operator has to behave in a sensible way. It needs to be reflexive, symmetric, and transitive. Why are these properties important? Because you can't understand your code unless you have them.
If I copy from A to B, well, I expect that A is equal to B and B is equal to A. Of course. Why wouldn't it be? If I then copy B over to C, well then C and B and A, they're all equivalent. It doesn't matter which I have because the only thing that matters there is the value, not the identity.
Fortunately, it's very, very easy to implement these things. I can just say, take CGPoints and extend it with Equatable and implement the equality operator, and when you compose value types out of other value types, generally speaking, you just have to use the underlying equal, equal operators of all the value types.
All right. Let's bring this back to our temperature type. We will make it a struct now. We are going to switch Celsius back to a var to we can mutate it. This now has value semantics. We give it the obvious equality operator. We go ahead and use this in our example before.
That's fine. We create the home, create the temperature, set the temperature to 75 degrees Fahrenheit and whoa! Compiler stops us here. What went on? Well, we are trying to mutate a property of temp, which is describes as a let. It's a constant. It can't change. We will appease the compiler. Change it to var, and now we can mutate it. And this all works perfectly fine.
Why is it fine? Well, the house points out to the thermostat in the oven. The thermostat and the oven both have their own instances of temperature values. They are completely distinct, they're never shared. They also happen to be inlined into the struct, and you are getting better memory usage and performance. This is great. Value semantics have made our lives easier here. With our example, let's go all the way and make everything value semantics.
Now, house is a struct that has a thermostat struct and an oven struct, and the whole world is value semantics. The changes we need to make to our code is now home can be mutated because we can go ahead and change the temperature on the thermostat of the home, right, and that's a mutation to home and thermostat and to temperature.
Okay. This brings us to a really important point. Value semantics works beautifully in Swift because of the way Swift's immutability model works. When you have a let in Swift, it's of a value type. It means this value will never change short of something corrupting your process' memory. This is a really strong statement.
It means it's very easy to reason about things that are let. But we still allow mutation. You can use var to say this variable can be changed. And that's extremely useful for our algorithms. Note that this change is very local. I can change this variable, but it's not going to affect anything else anywhere in my program until I tell it to, until I do a mutation somewhere else, which gives you this really nice controlled mutability.
With strong guarantees elsewhere. One of the nice things here is when you are using value types of passing them across thread boundaries, it gives you freedom from race conditions on those types. So I create numbers. I passed them off to some process that will do something asynchronously. I mutate numbers locally and then do it again. With a reference semantic array, this is a race condition. It's going to blow up on you sometime. With value semantics, you are getting copies each time, logical copies each time.
And therefore, there is no race condition. They are not hitting the same array. All right. Hold on. This sounds like a performance problem, right? We are doing a copy every time we pass numbers through a parameter. Okay. One of the important other pieces of value semantics is that it copies are cheap. By cheap, I mean constant time cheap.
Let's build this up from fundamentals. So when you have the fundamental types, the really low-level things -- integers, doubles, floats, et cetera -- copying these is cheap. You are copying a couple of bytes. Usually it happens in the processor. So then you start building structs out of doubles and ints and so on. Like CG points is built of two CG floats. And any of these structs, enums or tuples, they have a fixed number of fields, and copying each of the things in there is constant time. So copying the whole thing is constant time.
All right. That's great for fixed-length things. What about extensible things, strings, arrays, dictionaries, and so on? The way we handle these in the Swift world is by copy-on-write. So this makes the copy cheap. It's just some fixed number of reference-counting operations to do a copy of a copy-on-write value. And then at the point where you do a mutation, you have a var and then you change it, then we make a copy and work on the copy.
So you have sharing behind the scenes, but it's not logical sharing but logically these are still distinct values. This gives you great performance characteristics from value semantics and is really a nice programming model. So we really love the value semantic programming model. Different variables are logically distinct, always.
You have the notion of mutation, an efficient mutation, when you want it locally controlled. But you have these strong guarantees of let, meaning it will not change elsewhere. And copies are cheap, which makes us all work together. All right. With that, I would like to hand it over to my colleague, Bill Dudney, who will talk about value types and practice.
[ Applause ]
[Bill Dudney]
Thanks, Doug. Hi, everybody. So now that Doug has filled our minds with how value types work, how they compare with reference semantics, let's talk about building a real example that uses value types. So what we are going to do is put together an example where we build a simple diagram made of a couple odifferent value types, a circle and a polygon.
So we will get started with the circle. It's a center, and a radius. A couple of value types that come straight out of the standard library. Of course, we want to implement the equality operator, the equals equals operator, and we do that by just comparing those types. Again, since they are built into the standard library, all we have to do is use those since we are composing from the simple types that came out of the library.
Next up is the polygon. It has an array of corners, and each of the corners is just another CG point, which, again, is a value type. So our array is a value type, and, again, our comparison is straightforward, just using the equals, equals operator there to make sure we implement the Equatable operator. Now what we want to do is put these types into our diagram, put both polygons and circles.
Making an array of circles is straightforward. Making an array of polygons is straightforward. So we can make an array of either type. What we need to do is make one array that contains both. The mechanism to do that in Swift is with a protocol. So we will create a protocol called Drawable.
We will make both of our subtypes implement that protocol, and then we can put them into an array in our diagram. Tons of great information in this Protocol-Oriented Programming in Swift talk, which is repeating again today at 3:30. So if you haven't seen that, I would highly suggest you take a look at it or catch it on video.
So here's our Drawable protocol. Straightforward and simple, has one method, Draw, on it. And, of course, we want to implement that on our two types. We will create an extension of polygon, implement that draw method, and that's just going to call out to Core Graphics and draw the polygon.
And the same thing for the circle. So what we are going to do, just call Core Graphics to build up the representation of the circle. Now back to out diagram. It's got this array of drawables called Items. We need to create a method to add items. That's marked as mutating because that mutates self.
We are going to implement the Draw method to simply iterate through that list of items and call the Draw method on each item that's in the list. So let's take a look at it diagrammatically. So we create a diagram, called doc. We create a polygon and add that to the array.
We create another one, a circle, and we add that to the array. Now our array has two drawables in it. Notice they are different types. When we create another document, by saying doc2 equals doc, we get a logically distinct, brand-new instance. It's logically separate from that first instance.
I can go back and make changes to doc2 now, and when I do that, of course, it has no effect on doc. I change that circle to a polygon. The array has value semantics even though the collection is heterogeneous. So it has the polygon inside, and the circle is inside that array as a value.
So, of course, we want to make our diagram struct Equatable. So we implement the protocol. And this would be the straightforward implementation that we would look at doing. However, if we would do that, the compiler says, "Hey, wait a minute, I don't have an equals equals operator for these two values on either side of that equation." Again, I will refer you to the Protocol-Oriented Programming talk, where we talked through all the details of how all that works.
In this talk, we will focus on the value semantics. So drawable has a single method called Draw, our diagram has a method called Draw. So let's go ahead and turn our diagram into a drawable. All we have to do is add that declaration to it. Now our diagram quacks like a duck and is a duck.
So this brings us to an interesting point. I can create a new diagram and add it to my existing diagram. It's got three different types in there, but they are all contained inside that array. It's a new instance of Diagram. But I can push it one further and add that document to the array. Now if this were reference semantics -- let's look at the Draw method. If this were reference semantics, this would infinite recurse.
As I call Draw on my diagram, it will go through the list of items and find itself in that list. And so it's going to call Draw again and infinitely recurse. But, we are using values. So instead of the doc being added to my diagram, it's completely a separate and distinct instance because it's a value.
So there's no infinite recursion. I just get two polygons and two circles drawn. Now that we have talked about building a tree of objects purely out of value types, let's talk about how we mix value types and reference types. Now in Objective-C, you are used to putting primitive data types inside your reference types all the time. This is how we build things in Objective-C. But the flip side introduces some interesting questions that we have to think through.
If we are building a value type, we want to make sure that that value type maintains its value semantics, even though it has a reference inside of it. So if we are going to do that, we have to think about that question. How do we deal with this fact that two different values might be pointing to the same thing because it has a reference in it? So we have to solve that question. The other thing we have to think through is how is equality affected by that. So let's start with a simple example with an immutable class, UIImage. We will create an image struct that is going to be a drawable, and it has a reference to a UIImage.
So we create the instance with this beautiful photograph of San Francisco. And if we create another one, image 2, so now image and image 2 are both pointing to the same object. And you look at this and you think, Bill is going to trick us, and this is going to be a problem, and it will be like the temperature thing. But it's not because UIImage is immutable. So we don't have to worry about image 2 mutating the image that sits underneath it and having the first image be caught off guard with that change.
Of course we want to make sure we implement the equality and at first blush, you might look at this and think, okay, I will use the triple equals operator, which will compare the reference and see if those references are the same. Well this would work okay in this example, but we also have to think through what happens if we create two UI images using the same underlying bitmap.
We want those also to equate to being equal, and in this case, since we are comparing the reference, they would not. So this would be falsely saying that these two images are not the same. So instead what we want to do is use the Is Equal method that we inherit from NSObject on UIImage to do the comparison, so we'll be sure that the reference type gets the right answer on whether or not it's the same object or not. Let's talk about using mutable objects.
We have a BezierPath here. It also implements Drawable. But its entire implementation is made up by this mutable reference type, UIBezierPath. In the reading case, when we're doing Is Empty, everything is okay. We are not doing any mutation, so we're not going to mess any other instances up.
But on this one below, we have this Add Line To Point method, and if we have two BezierPaths pointing to that, it will cause problems. Also notice here, we don't have the Mutating keyword there. That's a sign that we know we are mutating because Add Line To Point is there, but the compiler is not yelling at us about it. That's because path is a reference type. We will look at that again in just a moment.
So if I have two instances of BezierPath, both pointing to the same instance of UIBezierPath through this reference, and I make this mutation, that's going to catch the other one off guard. This is a bad situation. We are not maintaining value semantics. We need to fix that. The way we will fix that is use copy-on-write, and we want to make sure that before we write to that path, that we make a copy of it.
So to do that, we need to introduce a couple of new things to our BezierPath. First, we want to make our path instance private, and next we want to implement this computed path property for reading and from there we will return our private instance variable. And we want to make a path for writing computed property that's marked as mutating, and that's going to, in fact, change the state. So we marked it mutating and we set path equal to a new copy of our existing path. Now we have both a reading copy and a way to get a writing copy.
And we change our implementation to reflect that. In the Is Empty method, we will call our reading copy, and in the mutating method below, we will call the path for writing. And the great thing about this that the compiler is going to yell at us and say, "Hey, that path for writing property is marked as mutating, and this method is not marked for mutating." So we are getting help from the compiler to help us figure out when we are doing something wrong.
Just to look through it in a diagram, the path, I create another one by saying Path To. Of course, I can read from it. No issue. And when I go to write to it, since I'm creating another instance of BezierPath, path two is none the wiser that a mutation has happened. So I will not introduce some unexpected mutation behind path two.
So now let's talk about how to use these things in practice. Here we have our polygon type, and we are extending that by adding a method that's going to return to us a BezierPath that describes that polygon. So we create the BezierPath, iterate through the points, adding a line two to each of these points. Now, the downside is remember that Add Line To Point method is copying on every call. So this is not going to perform as well as it might.
So instead, what we should do is create an instance of UIBezierPath and mutate that mutable reference type in place and when we're done, create a new instance of our value type with that BezierPath and return that. That creates only one copy or only one instance of UIBezierPath instead of multiple.
In Swift, we have a great feature where we know if objects are uniquely referenced, and so we can take advantage of that. This is a similar structure to what we saw in our BezierPath, and we can use this fact that we have this uniquely referenced property and we know for a fact that something is uniquely referenced so we can avoid making the copies if we know that that reference type is uniquely referenced. The standard library uses that feature throughout and does a lot of great performance optimizations using that. So that's mixing value types and reference types. You want to make sure that you maintain value semantics despite the fact that you have these references to mutable types by using copy-on-write.
So now I want to look at a really cool feature we can do now that we have a model type implemented as a value, and implement an undo stack. So I'm going to create a diagram and an array of diagrams. Then with every mutation, I will add my doc to my diagram array. So I create it and append. I add a polygon and append it to the undo stack.
I create a circle and append that to the undo stack. Now in my undo stack I have three distinct instances of Diagram. These are not references to the same thing, these are three distinct values. And so, I can implement some really cool features with this. So imagine this in an app, and I have a History button.
I tap on the History button and I get the list of all the states of my diagram back through my undo stack. I can allow the user to tap on something and essentially go back in time. I don't have to keep anything in some array of how to undo adding that property or anything. It just goes back to that previous instance, and that's the one that gets drawn.
This is a super-powerful feature, and, in fact, Photoshop uses this extensively to implement all of their history stuff. When you open an image in Photoshop, what happens behind the scenes? Photoshop slices and dices that photo, no matter how large it is, into a bunch of small tiles. Each of those tiles are values, and the document that contains the tiles is also a value.
Then if I make a change, like change this person's shirt from purple to green, the only thing that gets copied in the two instances of that diagram are the tiles that contain the person's shirt. So even though I have two distinct documents, the old state and the new state, the only new data that I have had to consume as a result of that is the tiles contained in this person's shirt.
So in summary, we have talked about value types, and what great features they bring to your applications, compared that to reference types and showed how value types fix up some of those issues. We talked through an example and saw some cool features that you can add to your applications by using value types. I look forward to seeing how those things play out in your apps.
Some related sessions that you can catch on video or if you have time today at 3:30 for the protocol-oriented talk. For more information, you can always email Stephan or go to our forums, and the documentation also has great information. Thank you very much and I hope the rest of your WWDC is great.
[ Applause ]