Transcript
[ Music ]
[ Applause ]
>> Good afternoon,
everyone, and welcome.
I'm Alex, and Jacob and I are
going to talk to you today
about how you can use
value types and protocols
to make your app better.
And we're going to focus today
on something called
local reasoning.
Local reasoning means that
when you look at the code,
right in front of you, you don't
have to think about how the rest
of your code interacts
with that one function.
You may have had
this feeling before
and that's just a
name for that feeling.
For example, maybe when
you just joined a new team
For example, maybe when
you just joined a new team
and you have tons of code to
look at but very little context,
can you understand what's going
on in that single function?
And so the ability to do
that is really important
because it makes it easier
to maintain, easier to write,
and easier to test, and easier
to write the code
the first time.
And so we're going to talk
about how local reasoning
can improve our UIKit apps
in the context of model view
controller design paradigm,
which is what Cocoa uses, where
the model stores your data,
the view presents that data,
and the controller
coordinates between the two.
And so we need a real app to
put our answers to the test.
Unfortunately, we actually
had a problem at Apple
that we needed to solve.
So I'm going to tell
you a little secret
about how we plan
WWDC every year.
Engineers have dreams and
that's what they're dreaming
Engineers have dreams and
that's what they're dreaming
about for what they're
going to present.
And so somehow we need to take
those dreams and record them,
but we noticed a lot
of engineers don't actually
remember their dreams
by the time they get into work.
They forget them.
So Jacob and I developed this
awesome app to do just that
and the app is called
Lucid Dreams.
I want to show you
some of the dreams
that people had over
the past year.
Some people dream of unicorns.
This is serious [laughter].
Strangely, some people were also
wrapped up in work stress issues
and even more wanted to just get
out of the office
and ride their bike.
And some engineers
even dreams of Crusty.
[ Applause ]
And so that was the
inspiration for our application.
But since we're going
to be using the app
throughout our session,
I want to give you a quick
demo so you can get a feel
for what this application does.
And so if we launch the app,
you'll notice it takes us right
to the list of dreams
that we've had.
And if you tap one of the
dreams, you can edit it.
And so you can see we
have a preview at the top.
We can scroll down and we're
going to add some effects,
like laser focus and rain.
And so if we scroll back up, you
can see the preview of a dream
of a unicorn with laser focus,
rain, and fire breathing.
And so when we're done editing
the dream, we can just go back
to the list of our dreams.
And so that's the app.
And we wanted to think really,
really hard about how we could
make our code just awesome.
And so we watched these
fantastic Swift talks
from last year.
These sessions focus on
the benefits of value types
These sessions focus on
the benefits of value types
and protocol-oriented
programming.
And these ideas were so powerful
that we wanted to take advantage
of them because they can
help improve local reasoning
in our application.
And so we wrote our app with
this different way of thinking.
Now some of these ideas
might feel foreign
to you and that's okay.
When we were initially
trying these techniques out,
they felt foreign to us too.
So don't worry.
Just stick with us.
And so we're going to
quickly go over the benefits
of value types in
the model layer.
This was already
covered last year,
so it's going to
be a quick recap.
Then we're going to focus on
how we can use value types
in the viewing controller
layers since that's
where we think most people don't
think they can take advantage
of value types, even
though there're some really
tangible benefits.
And along the way we're going to
show you how using value types
and protocols made
our code testable.
And as you just saw this awesome
app, I know you want to test it.
And as you just saw this awesome
app, I know you want to test it.
We've shipped this
as a sample app
so you can go download it
yourself, see the code,
and log your own dreams.
All right, so now it's time
to talk about the model layer.
So what is a dream?
A dream is our model type
that represents a dream
entry in our application.
A dream as a description, a
creature, and a set of effects,
as you saw in the UI earlier on.
Now I want to show you a
version of the dream type
that we used last year in
the first version of our app.
We started with a dream
type being a class.
Now classes have
reference semantics,
meaning that references
to the same instance
share their storage
and that sharing is implicit.
So why is that a problem?
Well, let's say someone tries
to modify dream2's description.
If we only care about
dream1, we may be surprised
If we only care about
dream1, we may be surprised
that the variable's
value changed
from underneath our control.
And this really hurts
local reasoning.
And we had exactly this kind
of bug in our own application,
even though our test passed.
But why was that?
And so this diagram
shows the relationships
of the first version
of our application.
Some of these relationships
can be explicit and implicit,
some of them can be
one-way or two-way,
and some of them can even
be dynamic or static.
And so these relationships can
get very, very complicated.
So what happens when we try
and test just the
dream type on its own?
Well, even if you create a
dream that stands by itself,
this doesn't reflect
the reality in the app
because there are
many more dependencies
that actually exist.
And so that's not good.
And so we can solve this by
making our dream type a struct
which has value semantics.
This means each variable
has independent storage.
So changing the value
in one doesn't change
the value in the other.
And so if we modify
dream2's description,
we only change dream2's
description, not dream1's.
And so this guarantees us
that dreams aren't involved
in the complicated relationships
that we saw earlier.
And so this really improves
our ability to reason locally
because no code can change
the value we're using
from underneath our control.
Now using value types --
So we just saw how
we can take advantage
of value types in
the model layer.
And using value types
in the model layer is actually
pretty uncontroversial.
But wouldn't you want to take
advantage of the same benefits
But wouldn't you want to take
advantage of the same benefits
that we just saw in other
parts of our application?
And that might actually
raise a few eyebrows.
So I want to share with you
a quote that I recently saw
on the Internet and that
said, "Use values only
for simple model types."
That doesn't seem encouraging.
But do we believe everything
that we read on the Internet?
And the answer is no, if you
didn't know [audience laughter].
And so for the remainder of
the talk, we're going to focus
on how you can use
value types for more
than just simply model
data in your app,
and at the same time we'll
prove the Internet wrong.
There we go.
And with that, I'd like
to hand it over to Jacob
to talk about the view layer.
[ Applause ]
>> Thanks, Alex.
I'm really excited to tell you
about how we use
protocol-oriented programming
together with views.
So we spent a lot
of time working
on our app's table view cells.
We had a specific design for
their layout that we wanted
to implement to make sure that
the unicorn the people dreamed
about showed up exactly right.
Now when we started our
app, we wrote these layouts
as abstract subclasses
of UITableViewCell.
For example, this simple layout
we called DecoratingLayoutCell,
it just shows a small
decoration on the left
and a larger content
area on the right.
Then, we made a concrete
subclass of the layout cell
that added our specific
logic, like showing a dream.
And we did this separation
because we wanted to be able
to reuse our layouts
in different places.
But as we worked
on the app more,
we found that this
wasn't working very well.
It helped us reuse our layout in
different cells, but it was hard
to use outside of a table view.
For example, we had
a detail view
that showed more
information about a dream
that showed more
information about a dream
but we couldn't reuse
our layout cell there.
So we wanted to find a
better way to structure this
where we could use our layouts
together with table view cells
but also in plain UI views.
And we also want to add
SpriteKit to our app
to show those cool particle
effects and we want to be able
to use our layouts with
those SpriteKit nodes too.
So those are our goals
and we use what we learned
about Swift to achieve this.
Now although I'll be talking
about layout in detail,
I want you to keep in mind
that these techniques can
be used all across your app.
All right, let's get started.
Now this is what our layout
cell looked like before.
It has two views that it lays
out, but there's really no need
for this layout logic to be
trapped inside of a cell.
It's just some math
and geometry to figure
out the right set of frames.
So let's change this from a cell
to just being a plain struct.
It will still have our two
views and we can put all
of that layout logic into a
single method that can be called
of that layout logic into a
single method that can be called
in to, to lay them out.
Now just with that small change,
we now have a really isolated
piece of code that knows how
to do our layout
and nothing else.
Then, we can update our dream
cell to use this new struct
to lay out its children.
And what's great is
that we can now use this
in our UIView subclass as well.
Now that this layout logic is
decoupled from table view cells,
we can use it in any UIView.
And there's another
great benefit with this.
Now that our layout can be used
in isolation, it's really easy
for us to run a unit
test for it.
We can just create some
views, add them to our layout,
and then lay them
out in a known rect.
Then, we just have to verify
that the resulting frames
are what we expected.
Our test doesn't have to
create a table view or wait
for the right view layout
callbacks to happen.
It can just tell our layout to
work and then verify the output.
And this is part of a general
benefit that we have now.
And this is part of a general
benefit that we have now.
Our new layout struct is
really small and focused.
This change has made
it much easier
to reason locally
about this code.
So if we want to understand
our tests to our layout,
we just have to understand
that small struct in isolation.
We don't have to
think about what set
of view capabilities it
might use or override.
Okay, now let's go back to
our DecoratingLayout code.
So right now, this still only
knows how to lay out views.
But as I said earlier,
we want to use this
to support SpriteKit as well.
So we don't want to have
to duplicate this code,
but SKNode is not a
subclass of UIView.
So there's no common
superclass that we can use here.
So how can we combine these
together into a single layout?
Well since the only thing
that our layout ever does
with these children is
to set their frames,
that's the only functionality
that we need them to have.
And we can represent that
requirement with a protocol.
So we'll make the protocol
and it'll just have a single
frame property for now.
and it'll just have a single
frame property for now.
This isn't very flushed out yet,
but we'll improve
it in a little bit.
Then, we use this
protocol as the type
of our children instead
of making them views.
And finally, we can use
retroactive modeling
to make UIView and SKNode
conform to our new protocol.
And now we have a layout that
works with both of these types,
and this is one of the
great things about relying
on protocols instead of
superclasses for polymorphism.
We can use this, add
this functionality
to unrelated types
to use both of them.
Now, our layouts no longer
have any dependencies
on UIKit as well.
And so another thing that we
could do is bring this same
system to AppKit
and support laying
out NS views just as easily.
I think that's pretty cool.
So we're really close now,
but there's something
here that we can improve.
When we're using a
DecoratingLayout in a view,
we want to be able to add all
of its content as subviews.
And similarly, when we're
using it in a SpriteKit scene,
And similarly, when we're
using it in a SpriteKit scene,
we want to be able to add
our content as child nodes.
But right now, content
and decoration can be any
type that has a frame.
And that means that for
example we could have content
that was a UIView and
decoration that was an SKNode.
But instead, we want our
layout to just have a set
of only UI views or only
SK nodes as its children.
And that way we'll
be able to add them
to their appropriate parent.
Now Swift has a great way of
expressing that with generics.
So we can update our
layout to be a generic type
with a type parameter
called child.
Then, we can make the content
and decoration properties
use that as their type.
And this gives us
exactly what we want.
We can enforce that they're
the same concrete type.
And so we can have
DecoratingLayout
with just UI views or one
that just contains SK nodes.
So generics are a great tool
that let us have
a lot more control
over the types in our code.
Another great benefit
of generics is
Another great benefit
of generics is
that the compiler
has more information
about what your code is doing.
So it can optimize more.
And you can learn about
this in a lot more detail
in the Understanding
Swift Performance talk.
It's a great talk for learning
how Swift works and how
to write fast Swift code.
Okay, we now have a
great implementation
of our DecoratingLayout.
But our app also includes
a lot of other layouts,
like this fancy cascading one.
And this layout is very
similar to the DecoratingLayout
that we just looked at.
They both show a large
area on the right
with a detailed decoration
on the left.
And we don't want to
copy and paste our code
to create this new layout
because that would miss
a great opportunity
to create a shared abstraction
that both of these can use.
So how can we share
this code instead?
Well, one tool you've
probably all used before
to share code is inheritance.
But with inheritance,
you have both your code,
and please don't try to read
this code, but you also have
to consider what your
superclass might be doing
and what your subclasses might
want to change or override.
So instead of just
thinking about the code
that you're working
with, your mind has
to pull together a large amount
of code that's spread
across your app.
And this is just the
tip of the iceberg.
A lot of the time you also
inherit from a framework class,
like UIView or view
controller and there's orders
of magnitude more code there.
So inheritance is another place
where you really sacrifice the
ability to use local reasoning.
But we can share code in a much
better way by using composition.
Composition is a simple idea
that's just combining smaller
pieces together to
build larger pieces.
But when composing, you can
understand those independent
pieces in isolation.
And you can also enforce
encapsulation without worrying
about subclasses or
superclasses poking holes
in your abstractions.
But composition isn't
new either.
You've probably used in the past
with Objective-C
or other languages.
And one way that we could've
made this layout before would be
to compose views together.
So you could've written a UIView
that does this cascading layout
behavior and another UIView
that does this cascading layout
behavior and another UIView
that does our decorating
layout behavior.
Then you could've added
both of those as subviews
in your table view cell.
But there's a big
problem with this.
Class instances are
very expensive.
When you make another object,
you have an extra
heap allocation
and this is even
worse with views.
There's a lot of work that's
needed to support a view
to allow to do things like
drawing and event handling.
And because of this,
we try very hard
to minimize the number
of views that we use.
So making a view that does
no drawing and only acts
as a layout abstraction
is very wasteful.
And that's why doing composition
with views doesn't
work very well.
But with Swift, we have a much
better way to do composition
and that's with value types.
Structs are very lightweight,
so we can use them together
without paying the high
heavy cost that we have
with classes and views.
And structs are also better
because of value semantics.
With value types you have
much better encapsulation
so you can use these pieces
together for composition
so you can use these pieces
together for composition
without having to worry
about someone else modifying
the copy that you're using.
So let's apply this
to our layouts.
Well, we can write the cascading
part of our layout like this,
with an array of
children that it lays out.
Then, we want to compose this
layout with our DecoratingLayout
to get the final effect.
But there's one more small
thing we have to change here.
These layouts only
expect to have children
that are UI views or SK nodes.
So let's generalize this
so that we can use layouts
and compose them together.
Well the layout protocol
that we're using
for our children
requires a frame property.
But we never need to call
the getter for that property.
We're only ever setting
new values for it.
And we don't actually care
if our children have
a frame or not.
We really just want to
be able to tell our child
to lay itself out
in a given rect.
So let's change this to a
method that reflects that.
When we decide a rect
for one of our children,
we'll tell that child
to lay out in that rect.
we'll tell that child
to lay out in that rect.
UIView and SKNode can still
conform to our protocol.
When they're asked
to layout in a rect,
they'll just use it
to set their frame.
But now we can make
our layouts conform
to this protocol as well.
They already know
how to do layout.
When they're given a frame,
they just divide up that rect
and give it to their children.
Now we also need to make one
small change to DecoratingLayout
to allow it to have
more flexibility
in the types of its children.
And we'll look at that in
detail a little bit later.
So now we can build
our fancy layout
by composing together a
CascadingLayout together
with a Decorating Layout.
The composition lets us build
advanced layouts like this
in a very declarative way and
there's even more examples
of this in the sample code.
So when you're working on your
own app, the next time you need
to reuse code or
customize some behavior,
trying using Composition.
It's a great tool.
So earlier I mentioned
that we wanted to be able
to add the contents
of our layouts
to either a superview
or a SpriteKit scene.
And an important part of
that is adding those contents
in the right order.
For example, our CascadingLayout
wants its children to be ordered
so that they line up on top
of each other, like this.
So let's expand our protocol to
be able to support that as well.
We'll add a property to our
layout protocol to be able
to return its contents.
And our combining layouts will
return all of their contents
in the correct order from this.
And then leaf views and nodes
can just return themselves.
But once again, if
we make the type
of the contents just be our
protocol, this would allow
for mixed environments
of UI views
and SK nodes as the contents.
And since we're adding
these children to a parent,
we only want to allow
a homogenous collection
of just UI views
or just SK nodes.
So to enforce this, we
can add an associated type
to our protocol.
An associated type is
like a type placeholder.
An associated type is
like a type placeholder.
The conforming type
chooses the concrete type
that it wants to use.
So our protocol's
associated type is
for what we'll be
putting in the array
of contents that our layout has.
This allows us to write
something that just knows how
to lay out views and specify
that its content type is UIView.
And similarly, we
can write layouts
that only have SK nodes
as their contents.
And this type safety
is really great.
But just like before,
we don't want to have
to write a separate
layout for views and nodes.
But with the generic
version of our layouts,
we can still have a single
layout that supports both.
And for our content type,
we'll just use whatever the
content of our child is.
And this means that we can make
a DecoratingLayout that works
with just UI views and one
that works with just SK nodes.
Both are strongly typed so that
we can pull out their contents
and know exactly what they are
and they can still share
all of the layout logic.
So associated types
are a great way
that you make your
protocols even more powerful.
So now that we have our
improved layout protocol,
we can also revisit the types of
our DecoratingLayout's children.
Now here they're
required to be the same.
And this works great if they're
both UI views but not if we want
to have a CascadingLayout
together with a UIView,
like we talked about
earlier for composition.
What we really want here
is for all of the contents
to have the same type.
So let's update our
layouts to reflect that.
We can change our struct to
have two different generic type
parameters, one for
each of its children.
Then, we can also add a
generic constraint to require
that those two types have
the same kind of contents.
And this lets us express the
exact restrictions that we want.
Our children's content
must be the same type.
So this is our finished
protocol,
which represents our
layout abstraction,
and this is a much
better protocol
that what we had earlier.
This now has real meaning.
It's a complete set
of operations
that represent what it means to
be a part of the layout process.
You can also see our sample
app to learn all of the details
about how all this works,
including how we also used it
to do layout for rendering
images on a background thread.
And one last place that
we can also take advantage
of our new layout protocol
is in our unit tests.
So we can write a struct
that has a frame property
and conforms to our
layout protocol
and then we can change our
unit test to use this instead
of UI views as the
children in our layout.
Now our layouts will
just be setting frames
on these simple structs.
And this means that our test is
completely isolated from UIView
and only relies on the logic in
our own layout and test structs.
So we're unit testing our
layout without using the GUI.
I think Crusty would be proud.
[ Applause ]
So those are some examples
of how you can use types
So those are some examples
of how you can use types
and protocols in ways you
might not have expected
in the view layer of your app.
And we also saw some great
general Swift techniques
that you can apply anywhere.
First, we looked at how you
can improve local reasoning
by using value types.
Then, we saw how you
can use generic types
to get better type
safety and flexible code.
And we also saw how composition
of values is a great tool
for customizing and building
up complex behaviors.
Now I'd like to bring Alex
back to tell you what we did
with controllers in our app.
[ Applause ]
>> Thanks, Jacob.
Now I'd like to focus on
how we can use value types
in the controller layer.
And we're going to talk
about this in the context
of our app's Undo functionality.
So we implemented Undo
for our list of dreams
and that worked really
great, but we noticed
and that worked really
great, but we noticed
that we had a small bug
where Undo doesn't work
for our favorite
creature feature.
Now to reproduce this, we can
tap the Favorite Creature row.
Right now we have
our pink unicorn set
as our favorite creature
but let's change
that to be the dragon.
And to finish the change,
we can just tap the Done
button at the top right.
All right, so we've modified
the favorite creature
but the problem is that if we
shake to Undo, nothing happens.
And so that's a bug.
So let's take a look
at our code and see why
that actually happened.
So we have two model properties
on our view controller right
now: one for our dreams and one
for our favorite creature.
This is a pretty typical
arrangement in the UIKit app,
especially as they grow larger
and your feature set grows.
And so again we started off with
just our dreams functionality
and so we implemented
Undo for that
and it worked, which was great.
But after we added the
Favorite Creature functionality,
we didn't have that Undo code.
And so the bug was just
that we forgot that code.
And so to fix this, we
could've added another code path
that implemented Undo for
our favorite creature.
That sounds like a
maintenance nightmare,
because now every time we
add another model property,
we have to add another code
path to implement Undo.
And so that doesn't seem good.
So we don't want that.
And so after that we took a
step back and wanted to figure
out a solution that
would [inaudible] better
as we add more model properties.
And so the solution is to
compose these model properties
into a single value,
our model struct.
And our undo logic is
going to work solely
in terms of that one type.
Note it's really important
that our model still have
value semantics and that's
because it's composed
of two other values.
And so this approach
is really great
because we now have
only a single code path
for our two model properties,
and if we add another
model property,
we still just have
one code path.
And so this is really great.
And so we can accomplish
this in code
by moving our two
model properties
on our view controller
into our new model struct.
And from there, all we have to
do is add a new model property
to the view controller.
So that's how we're going to
structure it, but now we need
to implement the Undo code.
So how do we do that?
Well, I want to show you first
the way that it's commonly done
and we'll see why
it's a little buggy.
And so on the left, we have
our view controller's current
model value.
And on the right, we have our
operations and our undo stack.
Now in the original
version of our app,
we thought of the undo stack
as a sequence of small steps.
Each step was responsible
for modifying first the model
and then the view to match.
For example, in the
first undo step,
For example, in the
first undo step,
we're going to remove the
dream that the user just added
and then we're going to delete
the row in that tableView.
And we can continue
with the next undo step.
And so in this undo step,
we're going to change the model
to be back to a pink unicorn.
And so this approach of mutating
individual model properties
and updating our view
independently is really easy
to get wrong and that's because
you need to match the change
in the model to the change
in the view, precisely.
And so failing to do so leads
to a lot of inconsistencies
between your model and
your view, and so you end
up with bugs like this.
And I'm sure all of
you have run into this.
I run into this all the time.
And it's really hard to debug.
But why is it hard to debug?
Well, let's take another look
at our Undo stack
that we started with.
Where do these undoable
changes actually come from?
Where do these undoable
changes actually come from?
Well, each undoable change
comes from our view controller
and each of those undoable
changes matters for order.
And as we add features
to our app,
these opportunities
for mistakes spread.
And so there isn't one place
in our code where we can reason
about the correspondence between
our model and our view updates.
And so this is bad because
it's really complicated.
So let's think about a
simpler way to handle undo.
What if instead of
recording small changes,
each entry in the stack
is just a whole new value,
a whole new model and so
now performing an undo
on the model is really simple.
Just replace the current model
with the one on the stack
and that way we don't even
need to worry about order.
And then we just
replace the value.
And so now that we have that
sorted out with our model,
we need to figure out how
we're going to update our UI.
we need to figure out how
we're going to update our UI.
And so in our view controller,
whenever a model changes,
we're going to call this
modelDidChange method.
Now, I recommend you go
download this sample and look
at this method for more
information about how it works.
But in that method, we need
to find the differences
between the old and
the new model values
and update our UI to match.
So for example, we
can check to see
if the old model's favorite
creature is different
than the new model's
favorite creature.
And if it is, we're going to
update our table view section
that contains the
Favorite Creature row.
And there's a more flushed out
implementation in the project,
like I just mentioned, so I
recommend you check out more
of the UI that we
update in there.
And finally, we can just
register the undo logic
where we reset our
model to the old value.
Now this is great because
now we only need one
or we're only registering
the undo in a single place.
or we're only registering
the undo in a single place.
So what were the benefits here?
Well, as we saw, we now
have a single code path
for updating our UI,
and operations are
order independent,
which was not the case before.
This really helped us reason
locally about our code,
about our UI update code.
We also saw how values compose
very well with other values.
If we have two values as
properties of a single value,
that value still
has value semantics.
Okay, so we just talked
about how to use value types
in the controller layer
with our model properties.
And now I want to do the same
with our controller's
UI state properties.
And so you've seen
this screen before.
It's just our list of dreams.
But this view controller has
many, many different states
and so I'd like to show
you the state diagram
for our view controller as it
relates to a very cool feature
for our view controller as it
relates to a very cool feature
that we have, the ability to
share dreams with friends.
So let's take a look.
So this is our basic
state diagram.
We're going to start off
in the viewing state.
Now we can tap the Share button
at the top right, which is going
to take us to the
selecting state.
And then we can select
dreams to share.
And then we can tap
the Done button
to go back to the sharing state.
Then when we're done, we just
go back to the viewing state.
And so that all worked well.
But let's go back
to the selecting state
again real quick.
You'll notice that we
can stop sharing midway
through by tapping the Cancel
button at the top left.
And so this moved us back
into the viewing state.
And you can see that our
navigation bar looks correct
since it shows the
Share button again.
since it shows the
Share button again.
But there's actually a
subtle UI bug in our app
because of an inconsistent
state.
The UI on the left side of the
table view is still visible
to allow users to select dreams
to share and this is wrong.
So when we went back to debug
this code, we saw that some
of our state properties
weren't fully cleared
out during a state change.
And so in this case, even though
we moved to the viewing state,
we forgot to clear out
some of the properties
of the selecting state.
So let's take another look
at our state diagram to see
if we can come up with a fix.
Now each state here has
a corresponding property.
And those properties
are properties
on our view controller.
And the number of
state properties
in our view controller
can easily explode
as your app's feature set grows.
And so it's important that in
this case our properties are
mutually exclusive.
And so when we're
viewing, we not sharing.
And when we're sharing,
we not selecting.
But the way we've written it,
when you set one property,
you need to clear out
all the other properties
and this is really error prone.
And so how can we
solve this problem?
Well, enums are actually perfect
for mutually exclusive values.
And so we turn all of
our UI state properties
into cases on an enum value.
And from there, we can
just add a state property
to our view controller.
So by using an enum,
we can make sure
that our states are
mutually exclusive.
And that's great because
now the invalid state bug
that we had before
isn't even possible
and it's enforced
by the type system.
And so this approach also means
that our state changes all
at once, without any possibility
of any intermediate states.
So we don't to coordinate
flipping properties
with implicit timing
dependencies.
with implicit timing
dependencies.
And as a bonus, having
your state all
in one places makes it
easier to launch your app
in exactly the same state
as the user left it.
So I really recommend
you checkout
and download the project again
to see how we implemented state
restoration in our application.
Okay, so we covered a lot today,
and we started off with the goal
of improving local
reasoning in our application
by introducing value
types and protocols
into our model view
controller based application.
So how'd we do?
Well, we started off by making
our model have value semantics,
by making the dream
type a struct.
And this makes it easier for us
to locally reason about our code
because there's no implicit
sharing of our dream variables.
Then Jacob showed you how
we built small components,
like DecoratingLayout
and CascadingLayout.
These small components
took advantage of protocols
These small components
took advantage of protocols
in generics to make sure
that the generic components
were reusable with views,
SpriteKit nodes,
and image rendering.
And all of this led to better
local reasoning since each
of these types were
small, testable,
and isolated value types.
And then we saw how
we can take advantage
of composing model properties
on a view controller
into a single type.
This made it easier to implement
undo with a single code path,
even as our model type grows
to have more properties.
And this approach also
gave us one code path
for updating our
UI, making it easier
to understand the UI logic
of our view controller
in isolation.
And finally, we saw how we can
turn our mutually exclusive
state properties into an enum
value on our view controller.
Now this reduced the
potential for our UI to be an
in inconsistent state.
And so those were the value
types that we discussed today.
And so those were the value
types that we discussed today.
But if you go and download
the sample project,
you'll see that there
are actually many more
in the project.
And so pretty much our
whole application is built
with value types except
for where we have a
controller or view object.
Now these are required to
be reference types by UIKit
but we've still moved most
of our functionality
into value types.
And so we spoke about a lot
today and I want you to go home
with just a few things in
your mind to take home.
So the first is customization
through composition
instead of inheritance.
The next time you're
at your desk
and you're drawing a class
diagram to solve some problem,
I want you to think how you
can use composition instead
of inheritance to solve that
problem, so you get the benefits
of value types that
we talked about today.
And the second technique
is to use protocols
for generic reusable code.
You can make small reusable
components that are easy
to locally reason
about and easy to test.
And so I highly recommend
you check out how we've done
that in the sample with
generic types instead
of having class hierarchies.
We also showed you how to take
advantage of value semantics.
The important thing
to remember here is
that if you have a value
composed of other properties
that are values, the larger
value also has value semantics.
And finally, we talked
about local reasoning.
Now, local reasoning is actually
a very general technique that's
not specific to UI
programming, it's not specific
to mobile development, and
it's not specific to Swift.
This is a really
important aspect
of all programming language,
all programming languages.
of all programming language,
all programming languages.
So when you get back to your
desk and you start coding,
I want you to think, regardless
of the language, does that code,
how well does that code
support local reasoning.
Now it's no accident that Swift
emphasizes value types so much
because they're a hugely
important aspect for you
to be able to locally
reason about your code.
And that's it.
So you can find the sample code
and more relevant
resources here.
I highly recommend you do that.
We have some related sessions
that we mentioned
throughout the talk
and I recommend you
watch them on video.
And thank you for a great WWDC.
[ Applause ]