Transcript
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Applause ]
>> 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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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 actually embracing the notion
of immutable reference
types throughout here,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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 --
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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 ]