Transcript
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Applause ]
>> Good afternoon.
Hi, everyone.
Thank you for joining us.
My name is Ted Kremenek.
I manage the Swift
team at Apple.
Alex and I have the real
honor and privilege to talk
to you this afternoon about
some great ways to use Swift
in practice to find more issues
in your code at compile time.
This is a pretty broad
theme, so we decided
to focus on two topics.
The first is about
taking advantage
of the new language
affordances in Swift 2
to easily allow your
application to take advantage
of new APIs while deploying
to earlier releases.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of new APIs while deploying
to earlier releases.
We talked about this
briefly in earlier sessions.
We are going to really
dive into the philosophy
of how it's designed, the
problems it was solving,
and how best to use
it in your code.
Afterwards, Alex is
going to talk about how
to use the rich type
system, protocols, enums,
and even protocol extensions to
enforce application invariance
in your own code and to define
away a lot of boilerplate.
So let's jump into
our first topic,
taking advantage of new APIs.
This is a well-told story that
many of you are familiar with.
As Apple, we continue
to roll out rich,
new APIs in every OS release
that gives you the opportunity
to build great features
in your application.
Right? This is part of the
reason why we do it; right?
The conundrum here is that apps
have existing users; right?
They are currently...
they are not necessarily
using the newest OS.
We have a rapid adoption
rate on iOS,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
We have a rapid adoption
rate on iOS,
but not necessarily everybody
adopts instantaneously,
and some of them can't adopt at
all for a variety of reasons.
So you're faced with
a series of choices.
What do you do?
Should you just go ahead
and require your app
to use the latest OS?
Right, so you get all the new
APIs, but this really sucks
because you are going to lose...
you know, you lose users.
These are the people
who are buying your app.
Should you, on the
opposite extreme,
hold back on adopting
new APIs at all?
Right, this just goes to
the least common denominator
of the earliest OS
that you support.
This is also bad because
you are holding back
on the possible rich features
you could be delivering
to your users.
And of course, you know,
there's the "have your cake
and eat it too," where you
can adopt new APIs while still
deploying back to
earlier releases.
So this is something that we
have supported technically,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So this is something that we
have supported technically,
you know, for a very long time.
Right, you can do this in
both Objective-C and Swift.
But the reality is this
is an extremely painful
experience today.
[ Applause ]
But in Swift 2, we tried to make
it as pain-free as possible,
and we've done that by
looking at the current problems
that developers have told us
about, you know, about deploying
to earlier releases
and trying to solve
that problem in the language.
Now, the fundamental model
hasn't changed; right?
To develop, just for our
platform, we always want you
to use the latest SDK.
Right? That essentially
gives you the full grab bag
of all the APIs you
could potentially use
in your application.
Then you toggle the
deployment target of your app
to say how far back in
time you want to go.
Now, pictorially,
this is pretty simple.
It's like a sliding
window of releases.
You set the base
SDK to the latest,
set the deployment target
back to the earliest release
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
set the deployment target
back to the earliest release
in that window; right?
Conceptually very simple.
So before I talk about how we
do adopt new features and APIs
in Swift 2, let's take a look
at the current problems we have
in the existing approach.
So fundamentally, you have to
write your app so it can contend
with the absence of APIs on
an earlier host OS; right?
So there are several things you
have to separately consider --
the absence of entire
frameworks, classes, methods,
functions, and even certain
enum values are not legal to use
on an earlier release.
And the thing that really is
the bummer is you have to reason
about each one of
these independently.
For frameworks, you have to
tediously decide, you know,
say hey, this framework
is going to be optional
when I link it into
my application.
If you don't do this, your app
is just going to fail a load
when it launches
on an earlier OS.
And then comes the actual
usage of the API itself.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And then comes the actual
usage of the API itself.
Let's start with classes.
So fundamentally, you
are writing your app
so it behaves differently.
There's going to be
conditional behavior here,
like when new APIs are
available, your app is going
to do something different.
So conditional logic
isn't really the problem.
The problem is how you actually
conditionalize your behavior.
Here on this slide, here
is the canonical way
that you check availability.
You query the Objective-C
runtime, say hey,
is this class present
at runtime?
The problem is that
this is a bit of a lie.
Right? I mean, so the
class may be available,
but that doesn't mean that
it's available for you to use.
Frequently, APIs start their
life as internal APIs, right,
in the OS, and they go through
some time where they are baked
and their evolved, and then
by the time they are released
for public use, the behavior
of the API may have
completely changed.
Even though this check
may succeed at runtime,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Even though this check
may succeed at runtime,
it's not the actual
truth of whether
or not you can actually
safely use this API.
What that means is if you use
the API too early, you know,
on an OS release too early
before you are supposed
to be using it, you
essentially have a time bomb
in your application.
The behavior of your
app that assumed
that that API would
behave a certain way is now
completely broken.
This has bitten developers
many times.
So this is a huge problem.
The other problem is it's
so easy to make mistakes.
Take this new API.
With a few characters'
difference,
I have completely valid code,
and it's doing the wrong thing.
And this data was introduced
long before NS data asset.
Your code will compile,
it will even run correctly
if you are testing
on the latest OS.
So the failure occurs only when
you happen to run this code
on an older OS device.
Right? And that's not going to
be in your common test scenario,
and in some cases it's only
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and in some cases it's only
up to your users to
find this problem.
And what will happen is you
will have a crash at runtime
when you try and use this class.
So easy to make mistakes
just by having simple typos.
Methods essentially suffer from
the same problems as classes.
You can type them wrong.
If you are checking for the
availability of a property,
now you have to know what the
selector was for that property
and you have to spell
it correctly,
and also the syntax is
completely different; right?
You are checking for APIs,
but the syntax is different.
Functions suffer from
the same problems.
You can make the same
mistakes, but yet you have
yet another third way
to write something.
And then if you have enums,
you are just completely hosed.
You don't really
have a good solution.
There is no respond
to selector for enums,
so you are stuck doing a
manual OS version check.
And just looking at this table,
like there's a whole assortment
of book keeping that
you have to have
in your head to get this right.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
in your head to get this right.
And this is just a
very sad story indeed.
So our observations was
this is just a really broken
programming model.
Right? It technically works,
but it's just hard to do.
And we want you to
take advantage
of new APIs while continuing
to support all of your users.
So we need to fix
these problems.
So in Swift 2, things
have changed.
It's built into the language.
You focus on how you want to
structure your app so that
when it makes sense to
have alternate behavior,
you just go and use those APIs.
You understand there's going to
be conditional behavior there,
but you focus primarily on that.
Then the compiler has your back.
It will emit an error if you
use the API in an unsafe way.
You also have unified syntax,
so you don't have to think
about classes, methods,
functions, even enums.
All of this is handled under
one syntax that you just use.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
All of this is handled under
one syntax that you just use.
And the compiler knows
the syntax for you to use,
so if you don't get it right,
it will tell you what to do.
And because the compiler
is all involved all along,
and in Swift we are
using modules,
all that optional linking,
that just gets handled for you.
So how does this work?
So here are some APIs
from core location.
Now, let's say I am
deploying to iOS 9,
so I am using the iOS 9 SDK
and set my deployment
target to iOS 9.
The compiler sees this
information in the SDK,
so this is literally in
the Objective-C headers,
which you could also view as the
generated interfaces in Swift.
So the class was
introduced in iOS 2,
and the method was later
introduced in iOS 8.
Since I am running on iOS 9,
there's no problems just using
this API unconditionally.
If I deploy back to iOS
8, still no problems.
But if I slide all the way
back to deploying to iOS 7,
the compiler can see, just
as we can read on this slide,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
the compiler can see, just
as we can read on this slide,
that it's unsafe
to use this method,
request when in use
authorization.
And the compiler will tell you
that this is just unsafe code.
And it's an error.
It will literally prevent
you from building this code.
And it will give you
a nice safety check.
[ Applause ]
It will give you the option
of different ways you
can solve this problem.
There is this note, you want to
guard it with a safety check?
There is a fix it attached
to it, and if you accept it,
your code is rewritten as this.
Now, there's a combination of
compilation, static enforcement,
and runtime enforcement here.
What's happening is this
hash available syntax.
Basically, everything
within that block
of code the compiler
scans to see if, you know,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of code the compiler
scans to see if, you know,
what is the most
recent iOS or OS X
or whatever release
that's needed
to execute those APIs safely?
And then it will use that
version that's mentioned
in the hash available to do
the appropriate runtime check
for you.
The compiler will
insert that in.
You don't have to guess how it's
done, it's done efficiently,
it's cached, so you can
just use it very safely.
So just by using the
information in the SDK,
we get this perfect fidelity,
so you get a really safe model.
So a few people have
asked why are we checking
against OS version?
Isn't this something we
have provided guidance
against doing in the past?
The reason is the
bookkeeping is so hard to do.
At least when you are querying
runtime, you get some kind
of truth, even though
that truth turns
out to be a lie in many cases.
Well, logically, when we talk
to app developers -- you --
you think about, you know, the
experiences you want to build
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
you think about, you know, the
experiences you want to build
in your app, it's all
staged in by, like,
what host OS your users
are running; right?
In each OS release, there's
like a wave of new APIs.
Those basically define
the set of features
that you could implement,
and your users are
on different versions of
the OS, and so they --
you know, they logically break
into categories of the behaviors
that your apps, you
know, can have; right?
And so this all just
varies logically consistent.
Also, it's not checking for
just the existence of one API.
It doesn't even really make
sense because you usually intend
to use a bunch of APIs together.
And it's not always
true that the existence
of one API implies the other.
That information is in the
SDK, and the compiler can do
that bookkeeping for you.
And the compiler being involved
is the real game changer here.
It makes it so the availability
checks are just reliable,
and you can assume the compiler
is doing the right thing.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So you get that peace of mind
that you are really defining
away the whole class of problems
because you have this
compile enforcement.
It also naturally goes to
multiple platforms as well.
So let's say I have this NSData
asset example from before.
If I want this code to
target both OS X and iOS,
I can simply extend the syntax
to say I am also checking
for this minimum availability
on this other platform.
The star indicates essentially,
you know, the all other cases,
which in this case
would be Watch OS.
We put it there explicitly
to call
out that there's
potential control flow here.
For other platforms that
aren't explicitly mentioned,
this if condition will
still be executed.
Essentially, there is
a true and a false.
We wanted to call out
that those branches,
they still will be taken.
So we wanted to kind
of explicitly call
out that behavior
for readability.
Now, the availability check
also naturally composes
with other affordances in
Swift 2 for control flow.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
with other affordances in
Swift 2 for control flow.
So let's say you have structured
your app so that you want
to do this check, and then
implement some feature
and otherwise do
nothing, just bail out.
Well, this composes nicely
with the new guard statement,
and you can just reshuffle
your code like this.
Everything below the
guard is guaranteed
to have the availability
provided
by the hash available tag.
[ Applause ]
And so this naturally
composes to find a nice way
to factor your application.
So let's say you are
deploying back to iOS 7.
So I am going to color code the
APIs that are available at iOS 7
in green, and the green bar
represents within this block
of code, it's safe to use
anything from iOS 7 or earlier,
so this is basically
the compilers,
you know, view of the world.
If I want to use an iOS 8
API, which I have color coded
in orange, you have to do the
availability check; otherwise,
you get an error
from the compiler.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
you get an error
from the compiler.
You can think of it that
within this block of code,
you are raising the privileges
of what APIs are
allowed to be called.
Once I get out of that block,
my privileges drop again,
where I can only
call iOS 7 APIs.
If I want to call iOS 9 APIs,
I can do a different check
which then gives
a different range
of privileges within that block.
It's a very composable,
readable model.
Let's say I am building an app
that has a whole new different
set of features depending
on whether certain
APIs are available.
I want to factor it out.
I am not just loading a bunch of
code into these if statements.
I want to factor it out
into separate functions.
This is easily done.
I can declare another function.
Let's say for pedagogical
reasons, my function uses iOS 8.
I am going to call that
from my conditional block.
And the problem here is
the compiler doesn't know
that you are going
-- that you know --
that you are only going to
call this once you've done
this check.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So by default, the compiler
is going to go okay,
you are targeting iOS 7.
I am going to assume you
can only use iOS 7 APIs
within this function.
So if you want to use
iOS 8 APIs, then you have
to do the check there.
This is not really great; right?
This does not provide a way for
you to really factor your app.
It also results in
redundant checks.
You can tell the
compiler your intentions.
So the SDK itself has these
@available adaptations
on all the methods and classes,
saying this is the minimum
OS you can use for this API.
You can use those same
annotations on your own code.
What that means is you can't
even call this function unless
you've done the appropriate
availability check.
And once that happens,
the compiler sees your
code in a different light.
You can then completely
eliminate that check, you know,
that extra availability check,
and safely use iOS 8 APIs.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
This is nicely composable
because you have other
functions similarly annotated,
and you can just go
directly call them,
if the functions essentially
have the same API privileges.
And if you wanted to call APIs
with even more privileges,
that's when you have to
do the availability check.
So this is very nice,
composable, easy to reason
about way about how
to factor your code.
This applies, as you would
expect, to methods as well,
so you can mark -- a
class can be available,
but individual methods
might not be.
So if you want, you can possibly
instantiate the class before you
can call a specific method
at a higher availability,
you would have to do the check.
This also naturally works if you
want to mark the entire class
as requiring a certain
minimum availability.
If you did that, can't even
instantiate the class unless
you've done the availability
check.
So you get this really, you
know, total transitive closure
of completeness of
the API availability.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of completeness of
the API availability.
And this naturally leads to some
really great tricks you can do.
So let's say you had some
custom, you know, blurring view
that you had on earlier iOS
releases, and then Apple rolls
out a more specific subclass
of UI view that you want to use
on host OSes using
a new release.
You can do this kind
of runtime polymorphism
with the availability guard
so that if you are running
on your OS, use the
native, you know, UI.
Otherwise, use your custom one.
And then the client that goes
and gets this object
back doesn't need to care
about which OS version
you are running on.
You have completely provided
this separation of concerns.
And this works really nicely
for you can do the same thing
with protocols, providing
different implementations,
you can have closures,
different functions.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
you can have closures,
different functions.
It gives you completely new ways
in which to factor your code out
and get the safety
that you expect.
So we think the availability
checking is pretty awesome.
It -- I think it really
provides a cohesive,
safe way to adopt new APIs and
deploy back to earlier releases.
The unified syntax provides a
really safe programming model,
but more importantly,
it gives you a way
to naturally factor your apps.
You can read your apps.
You can read the code and
understand how, you know,
the invariance that
you are expecting.
And I think that's
incredibly powerful.
And on that note, I am going
to hand it over to Alex,
who will talk about other ways
you can use Swift's powerful
type system to enforce
your own invariance
in your own application.
[ Applause ]
>> ALEX MIGICOVSKY: Thanks, Ted.
Hi, everyone.
My name is Alex Migicovsky,
and I am a sample
coder here at Apple.
For the past year and a half,
I have been teaching
developers how
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
I have been teaching
developers how
to write Cocoa apps with Swift.
I have started developing lucid
dreams about Swift and Cocoa
and how you can use the two
together to have safe --
compiled time safe applications.
What I want to do today
is go through some
of these visions I've
had and explain some
of the paradigms you
can use from the visions
and apply those concepts
in your own applications
so you can have compile
time safe code as well.
Now, I haven't told you this
yet, but in these visions,
I also dream about unicorns.
I have developed a unicorn app
that lets me go see and browse
through the different unicorns
I have seen in my visions.
And the first thing
that I want to talk
about in my application is
Asset Catalog identifiers.
This is something that everyone
uses in their UIKit apps.
Now, my unicorn browsing
app is pretty simple.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
I have three unicorn images
that I have added
to my Asset Catalog.
But what I want to
do now is take a look
and see what the code looks
like when I actually create the
images from the Asset Catalog.
And you'll notice that I am --
here I have three images
that I am constructing.
Each of them I am
passing a string
to the UIKit UI image API.
And UIKit doesn't know what
assets I have actually provided
in my Asset Catalog, so I
have to unwrap these images
if I actually want to use
them in my application.
This is really unfortunate
because I have already defined
the Asset Catalog identifiers
in my Asset Catalog.
I don't want to have this
duplicate information here.
Furthermore, right here I
am only using three images,
but throughout my application,
I could use many, many more.
The problem is it's really
hard to find in your code
where you might have
typos, and you might want
to change these based on
the reaction that you see
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to change these based on
the reaction that you see
on the slide with
all these typos.
I know I do.
But it's really hard to
go back and find all that.
So a classic solution
to that would be
to have a global constant
where you would use the same
constant name everywhere
in your application.
And so if you use
that correctly,
you will get your unicorn
image back like you expected,
but you still have
to unwrap the image
because the compiler
doesn't know
and the framework doesn't know
if you are providing a
valid constant name or not.
And furthermore, you can
provide totally random streams
to the API and get a
fatal error at runtime
because it's the NSUbiquity
identity change node
notification is still a
string and is valid to pass
in UI image named API.
Let's talk about how we
can solve this problem.
What we have right now is
a stringly typed solution.
We are passing strings
everywhere in our code.
What we really want is a
strongly typed solution.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
What we really want is a
strongly typed solution.
What we want to be able
to do is map strings
to a new kind of type.
This is going to allow
us to encode information
about how we structured
our application right
into the compiler's knowledge
so that we can return
non-optional UI images
everywhere in our code.
The solution to that is going
to be application-specific
enumerations,
enums that we define
in our own application.
If we take a look at the
code that we defined before,
this is what I really want
to have my code look like.
I want to pass an enumeration
every time I create a UI image
object so that I don't have
to unwrap the return
value in my code.
So how do we implement that?
Well, the first thing
that I want
to do is define a nested
type on UI image that's going
to provide the mapping
between enum cases
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to provide the mapping
between enum cases
and the string representation
we have defined
in our Asset Catalog.
We defined it as a nested
type so other assets
that can be stored in Asset
Catalog can have this,
can use this approach as well.
Once I have done that, it's
pretty straightforward.
I can provide a case
mapping between an enum case
and string representation.
I can do that for all
my other cases as well.
Now, one really beneficial
thing about this approach is
that if I accidentally
have typos, if I copy
and paste a string
accidentally from somewhere
and have a duplicate,
the compiler is going
to give me a warning
or an error telling me
that I have a duplicate
case in my enumeration.
So that's really great that the
compiler can help me with that.
Now, once we've defined
this new type,
all we have to do is go back and
write a convenience initializer
that takes in this enumeration
instead of the string
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that takes in this enumeration
instead of the string
and forwards the
enumeration's raw value
to the UIKit defined UI
image named initializer.
If we go back and
look at our code,
we get unicorns everywhere we
wanted them, and if we look
and see what happens when we
have a typo like we had before,
the compiler now can tell
us that we've had a typo
since we've encoded
that information
of our application
structure into our code.
So if we fix that, the
compiler error goes away.
So let's talk about the
benefits of this approach.
The first thing is we have
centrally located constants.
If I were to add a new image to
my Asset Catalog, I know exactly
where I need to go to add
another image constant case.
The other benefit is
this doesn't pollute the
global namespace.
I could have multiple
objects that can be defined
in an Asset Catalog that
I use this approach for.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
in an Asset Catalog that
I use this approach for.
One of the best things is
that you can only use one
of these enum cases
in your application
when you are constructing
your UI image object now,
so the compiler can
help you with that,
and now you can return
non-optional images everywhere
in your code, so you
don't have to worry
about forced unwrapping.
So this was a very specific
approach that we're using
in this unicorn browser
app, but I want you to think
about how you can use
enumerations in your own code
to provide other kinds
of rich mappings.
You can use more than
just strings as well.
You can use integers
and even selectors.
So there's a lot of opportunity
to define these mappings
in your own code.
Now, that was a deep dive into
enumerations, but what I want
to talk about now are
segue identifiers,
since this is something
that you also use
in your code all the time.
Now, recently, my visions
have been getting stronger,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, recently, my visions
have been getting stronger,
and I've had to develop an app
that lets me actually keep track
of unicorns and download
them on the fly.
So I have a more
complex application.
And if we take a look
at the storyboard,
it ends up being pretty simple.
I have a single view
controller that can segue
to two other view controllers.
And for each of these
view controllers,
I've defined a segue identifier.
What I want to do now is take a
look and see what the code looks
like when we override
prepare for segue
to configure the view controller
that the unicorn browser view
controller would present.
So we've overridden this
method, and the classic way
of implementing this would
be something like this,
where we switch on the
segue identifier string.
Now, you saw before I am
using the same exact strings
that I defined as my segues
that I had in my storyboard,
but the compiler
doesn't know this.
So when I switch on
these two strings only,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So when I switch on
these two strings only,
the compiler is going to tell me
that this is not an
exhaustive check, and so I have
to add a default case because
the compiler doesn't know
that I have actually
provided valid mappings.
But what happens if I were
to add a new view controller
and I have to -- I
have a brand-new segue?
How do I know where in my code
that I actually need
to change this logic?
Well, let's take a look and see
how we can solve this problem
by using enums again.
So I've defined a nested type on
unicorn browser view controller
which is going to
represent the mapping
between segue identifier cases
and the string representation
in our storyboard.
So let's see how we can
implement the prepare
for segue method with
more stronger typing.
So the first thing
that I am going
to do is grab the
segue identifier string
from the storyboard segue object
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and construct the
segue identifier enum
from that raw value.
And I am going to
provide a little bit
of runtime checking
debugging just
in case I haven't
provided a valid enum case
for that new segue identifier.
Now, from there I can switch on
the enum instead of the string.
And that's really great because
all I have to do now is switch
on two cases, the compiler knows
that I've only defined two cases
in my enumeration, so that's all
I have to switch on in my code.
Now, if we add a new segue
identifier in our enumeration,
the compiler is going
to actually tell us
that we don't have an exhaustive
switch, and so everywhere
in our code that we
switch on this enumeration,
the compiler is going
to tell us exactly
where we need to
update our logic.
Now, this is a huge benefit
over the string solution.
So this is how you can
override prepare for segue,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So this is how you can
override prepare for segue,
but sometimes you need
to invoke perform segue
with identifier manually.
And in our case, I might want
to import a bunch of unicorns,
download them, download
images for them on the Web,
and then present a
new view controller.
So let's take a look and see
how that code might look.
So a classic solution to
this would be to pass strings
in the perform segue
with identifier method.
But we've already
defined this enumeration.
We really just want
to use this mapping
that we've already provided.
So we want to use enums instead.
How do we do this?
Well, it's actually
pretty straightforward.
We can define an overload on
the UIKit define perform segue
with identifier method that
now takes enumeration instead
of a string and then call
the UIKit define method
with the raw value
of our enumeration.
And if we go back to the
code that calls this method
with the enumeration, it
works exactly like we want it.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
with the enumeration, it
works exactly like we want it.
So this is a really
great solution specific
to unicorn browser
view controller.
But what happens when
we scale this up a bit?
I want to take a look now at the
structure of what we just did
to see how we can
apply this to more
than just the unicorn
browser view controller.
So what we've really done
is provided a mapping
between enum cases and
their string representations
in the storyboard.
And we've also added
implementation that uses
that mapping to have
a stronger --
a more strongly typed
system in our application.
But if we add a new view
controller and we want
to do the same thing, we are
going to have to duplicate all
of that work, and I
don't want to do that.
What I really want to do is
extract the implementation
from the view controller and
define a loose mapping for what
that enumeration of segue
identifiers is going
to be specific to a view
controller, and by doing that,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to be specific to a view
controller, and by doing that,
we can use this implementation
for many different kinds
of view controllers, regardless
of their class hierarchy.
So you can avoid a lot of
awkward class hierarchies
by reusing this implementation.
And we are going to do
this by using protocols.
And so I have defined
a new protocol.
We are going to call
this segue handler type.
And this is what our view
controllers are going
to conform to.
And now we want to define the
mapping that we just mentioned,
and now it's going to be our
segue identifier enumeration.
And we want to make sure that
the segue identifier conforms
to the raw representable
protocol.
This protocol is an
underlying implementation detail
of how enumerations that are
backed by other types work.
And the compiler can synthesize
this automatically for you.
So that's all we have for
the protocol definition.
What I want to do now is use
a Swift 2.0 feature called
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
What I want to do now is use
a Swift 2.0 feature called
constrained protocol extensions
to actually add the
implementation that's going
to be our generic reusable code.
So we are going to extend
this segue handler type,
and then we are going
to constrain it,
so we only want implementation
to be added
if these constraints are met.
And first constraint is
that the type that conforms
to this protocol is a UI
view controller subclass,
and this is going
to now allow us
to call UI view controller
methods
in our protocol extension.
And the second thing
is that we want
to make sure the segue
identifier mapping is
between enum cases and strings.
Now that we've defined this
constrained protocol extension,
all we have to do is
add our implementation.
All we need to do is take
the existing implementation
of perform segue with identifier
that we defined previously
and slap the exact same
implementation right
into the code.
And if we go back to our
unicorn browser view controller,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And if we go back to our
unicorn browser view controller,
all we have to do is add this
conformance to the new type,
and we've already satisfied all
the associated type constraints
of the protocol because we
already identified this segue
identifier enum.
So if we go to our handle action
method, we can call the code
in the exact same way, but we
are reusing the implementation
in our segue handler type.
So that was how you can
reuse the perform segue
with identifier method, but what
about calling prepare for segue
or handling prepare for segue?
What I want to do now is define
a convenience method that's
going to take in a
storyboard object
and return a segue identifier
enum, and this is going to be
in our protocol extension.
All I need to do again is take
the exact implementation we
defined before and return
the segue identifier enum
that we created out
of that code.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And so if we take a look at
our prepare for segue method,
it ends up being
really straightforward.
All we have to do is
switch on the result return
by segue identifier for
segue, which is the method
that we just defined, and we
only need to provide two cases
that we are switching
on the same exact way
that we did before.
But we have this
generic solution.
So let's talk about the
benefits of this approach.
So the compiler knows exactly
the segues that we've defined
in our storyboard
now when we add them
to our segue identifier enum.
So it can make sure
we handle all
of the possible cases
in our code.
We have a reusable solution by
using this protocol extension,
we can use this implementation
on any view controller
that conforms to
this new protocol.
And we also have
convenience syntax.
We can use method syntax
on these different --
on these different
view controllers.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
on these different
view controllers.
It doesn't have to
be free functions.
So this is a really specific
way that you can use protocols
and constraint protocol
extensions
in this unicorn browser
app, but you all have a lot
of other interesting
applications,
and what I want you
to do is think
about how you can use protocols
and associated type constraints
in your own applications to
encode some of the structure
of your application to the
compiler so it can help you
with compile time safety.
I want you to also think
about how you can use
protocol extensions
to share implementation
throughout your application
and avoid a bunch of awkward
class hierarchy problems.
So Ted and I spoke about a lot
today, but what want you to get
out of this is that the
compiler is here to help you.
Ted spoke to you about how
you can safely take advantage
of new APIs.
Now, this is largely
done by the compiler,
and so the compiler knows what
is available and what is not
for specific OS versions.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
for specific OS versions.
And I spoke to you about strong
typing in your applications
and forcing application
invariance
and leveraging the compiler,
letting it know what
the constraints are
of your application and encoding
that information into your code
so that the compiler can help
you reason about problems
at compile time and
not at runtime.
So for more information,
I suggest you go watch the
Protocol-Oriented Programming
in Swift session online, and if
you want to have lucid dreams
about Cocoa and Swift
yourself, I suggest you go check
out these two samples.
The lister sample has an example
of the segue handler
type protocol,
and DemoBots uses a bunch
of interesting ways it use
enumerations in protocols
for compile time safety.
Now for more information,
you can check
out the Swift Language
Documentation.
We have the Dev Forums,
and you can contact Stefan
if you have any questions.
Thanks, everyone, and
I am looking forward
to seeing you at the labs.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Applause ]