Transcript
>> Hello.
[ Applause ]
Thanks for coming.
My name is Tony Parker.
I'm the manager of the
Foundation Team at Apple, and
I'm here today with my
colleagues Michael LeHew and
Itai Ferber to tell you What's
New in Foundation.
We have three topics today.
The first, we're going to go
over really quickly some new API
highlights from this year's
release of Foundation.
After that, we're going to go
into the first of our two major
topics, the key paths and key
value observation APIs.
And after that, we'll talk about
our new encoding and decoding
APIs.
So let's get started with those
new API highlights.
First, this year, we've enhanced
the FileProvider API that we
introduced last year as part of
iOS 10.
This enhances your ability to
communicate between FileProvider
extensions and other
applications.
We've improved our available
storage space API.
This is new API on MSURL that
lets you not only get an idea of
how much free space is available
on your customer's device but
also how much space can be made
available if we purge
unnecessary content like caches
or old data.
We've added new API to NS range
and Swift range to help convert
between NSString's use of NS
range and Swift's string and its
range.
This is especially useful for
classes like AttributedString
and RegularExpression.
In NSXPCConnection, we've added
support for, better support for
NSProgress.
And this is actually really
interesting for that first item
because the new, enhanced
ability for communication
between extensions and apps is
actually NSXPCConnection, which
is available this year on iOS
for the first time.
NSURLSession has also gained
support for NSProgress.
And so what we're hoping is that
you can use all of these
progress features together to
flow progress from a download to
the extension, to an app, and
display something to your user.
And finally, we've brought the
thermal notifications from Mac
to iOS this year.
For more on many of these
topics, check out What's New in
Cocoa.
It was this morning, so if you
missed it, we had a pretty big
section on Foundation in that
talk.
Performance was also a really
key consideration for us this
year on Foundation, and that
started with a new copy-on-write
behavior for NSArray and its
dictionary, NSSet, and their
mutable friends.
So a huge motivator for this was
bridging into Swift.
So when an NSArray is returned
from an Objective-C API, perhaps
in a framework, and you use it
in Swift, you're receiving the
Swift value type: array,
dictionary, and set.
And to preserve value semantics,
those structures call copy on
the reference type when they do
that bridging.
If the result happened to be one
of the mutable subclasses, then
that copy could be pretty
expensive.
Now, we can defer the cost of
that copy until the time that
it's actually mutated, if that
happens at all, which can result
in really big improvements for
performance when bridging.
Struct data, a part of the
Foundation's Swift overlap, has
also gained a lot of performance
enhancements.
In particular, something really
cool -- we're able to actually
inline critical parts of data's
behavior into your app when you
compile.
This can include things like
indexing byte by byte in a data.
And again, that leads to some
really impressive performance
improvements.
NSCalendar has gained a better
performance, both in CPU time
and with lower peak memory, and
in addition to that, we've
actually improved the results
that it gives you, especially in
corner cases.
And finally, we've improved the
performance of bridging NSNumber
to and from Swift, and we've
improved its behavior in corner
cases as well to provide better
safety when you are converting
things from NSNumber to Swift
types like integer, Boolean, and
so forth.
This faster bridging has a big
impact on things like property
list parsing.
For more on many of these
topics, check out Efficient
Interactions with Frameworks.
That's Friday at 1:50.
Now, with that, I'd like to turn
it over to my colleague Michael
to talk about key paths and key
value observing.
[ Applause ]
>> Thanks, Tony.
Hi, I'm Michael from the
Foundation Team, and I'm excited
to share some improvements that
we're making to key paths and
key value observing this year.
And I'd like to start by saying
something that we feel fairly
strongly about on the Foundation
Team.
And that is that key paths are
incredibly important in Cocoa
development.
And this is because they let us
reason about the structure of
our types apart from any
specific instance in a way
that's far more constrained than
a closure.
And this is so important that we
felt that they weren't, that
warranted special treatment in
the language itself.
And we started on this last
year, when we added string key
paths to Swift 3.
This was the, this added the
ability for Swift to, at compile
time, confirm the correctness of
an Objective-C key path, which
I'll review now.
Let's suppose we have a class
Kid, and it has some key value
observable properties, such as
their nickname, their age, and,
of course, their current best
friend of the moment.
We can go ahead and construct an
instance -- in this case, a
little boy by the name of Benji
-- and then form a string key
path to a kid's nickname
property.
And then, the Swift compiler
will confirm that this is a
reasonable thing for us to do.
We can then use key value, or
key value coding to read or
write that variable back into
the instance.
Now, while this compile time
check that we get with using a
string key path expression is
pretty awesome, in the end, it
still compiles down to a string.
And in order for that string to
be useful, we need to use the
Objective-C runtime, which, last
I checked, still remains
unavailable for Swift value
types and probably won't be made
available any time soon.
Finally, string key paths carry
no type information.
It's just a string.
And so all general API that uses
string key paths needs to be
defined in terms of any.
But this is Swift, and surely we
can do better.
And so we thought about what
would, you know, key paths, what
should they be in Swift?
Well, we'd want to be able to
describe properties first, so
that's pretty essential.
They should be statically
type-safe.
They should be fast as well.
And they should work with all
the kinds of values that we
encounter in Swift.
And they also should work on all
platforms where Swift is
supported.
And so we thought for a long
time about how to make all these
key paths' dreams come true,
ultimately sharing our idea with
the world through the open
source Swift evolution process
with a document entitled SE-0161
Smart Key Paths.
And here's what the new Swift 4
key path literals look like.
They start with a backslash,
followed by the name of the base
type, a dot to indicate that
we're doing something inside
that base type, and then the
name of the property.
And the backslash here is really
important because it helps us to
disambiguate the execution of a
property versus a mention or a
reference to the property.
And of course, this is Swift, so
when we can infer the base type,
we do, though the backslash and
the dot remain.
Key paths can be composed in
sequence, just like calling
property after property, after
property.
And soon, optional chaining will
work as it does with properties.
We'll also soon allow
indirection through subscripts.
Someone likes that.
I like it too.
[laughs] Key paths can also
begin directly at a subscript,
and here we're starting with
data's byte subscript, and we're
using the startIndex here.
And of course, this can be
inferred as well, though the
backslash and the dot remain for
consistency.
These new key path expressions
offer uniform syntax for all
Swift types that support
properties, whether they are
stored or computed.
And of course, forming key paths
is one thing, but how can we use
them?
Well, let's suppose we have a
key path.
In this case, to a kid's age.
Using the key path to read a
property is as easy as invoking
a subscript.
That's starting to look like
code, so I'm going to go ahead
and syntax highlight now.
There we go.
So there's a little bit going on
here, and I want to talk about
some of the motivations behind
why this looks the way it looks.
First, we gave the key path
subscript parameter a label, and
we did this because we wanted it
to be non-ambiguous with other
subscripts that may exist on
other types.
Next, the type of the value that
you're using, invoking the
subscript on needs to match the
base type of the key path.
And if they match, it's a
reasonable thing, and your code
will compile.
You can also use subscripts to
mutate a particular value.
And subscripts are nice because
they offer a fast and symmetric
syntax for reads and writes into
a value, whether they be a value
type or a reference type.
Now, let me, I've been showing
these with reference types now,
but now I want to switch and
show how they work with value
types.
And to do this, we're going to
expand our example to what I
really wanted to talk about,
which is birthday party
planning.
So let's go ahead and create a
party.
Benji's going to have a
construction-theme birthday
party, it looks like.
And reading from a value type
with a key path that uses the
same subscript syntax that we
saw with our reference types.
Similarly, mutating a party uses
the same subscript syntax.
There's a common theme here.
The syntax really is uniform.
However, since this is Swift, we
know that Ben's party is a
birthday party, and so the
language can infer that for us.
And I just heard that Ben
changed his mind about his
birthday party theme again, so
let's fix that while we're at
it.
Now, here I'm just highlighting
the syntax.
In code like this, you would
just call the properties, so
let's look at what's actually
happening when you use these key
path expressions.
Key path expressions are
actually producing concrete
values, and like all values,
they can be stored.
Well, what is the type of this
variable?
Let's pretend we can
Option-click in X, just like we
could in Xcode and see that, not
surprisingly, we're getting back
a strongly typed key path with
the base type being a kid and
the property type being a string
because nicknames are strings.
Strongly typed key paths also
work with compound key paths.
Here we see that we started a
birthday party and traversed
through to the celebrant's age.
And of course, age is a double
because if you've ever known a
young kid, the numbers after the
decimal point are very
important.
Key paths stored in variables
can be used just like the
literals.
And because they're strongly
typed, they are statically known
to have the right type -- here
double, as we expected.
Let's suppose we had another
birthday party to plan.
This time, it's Mia's, or Ben's
little sister Mia.
We can use the same key path
variable to find out which
birthday she'll be celebrating,
and in this way, key paths kind
of serve as an unexecuted
property invocation.
Now, in this example, I'm hard
coding the celebrant's age, but
let's go ahead and generalize
this a bit.
What if I wanted to know the age
of anyone relevant to a party?
We're going to go ahead and
define a function, and we're
going to call it the
partyPersonsAge function.
They're given a party, and a key
path to a participant will
return their age.
And to do this, I'm going to
show another feature that these
type-safe key paths have, and
that is the ability to
dynamically form new key paths
from other key paths.
And so here I'll append two key
paths together, the
participantPath to a kid's age.
And again, we're inferring kid
here, which is why you don't see
the word "kid" there, except for
in the variable name.
And as you would expect, you get
a strongly typed key path that
starts at a birthday party and
goes to a double.
Using the key path is the same
as any other key path stored
into a variable.
And we can go ahead and call our
function on the celebrant in
that we have the same exact
result that we saw previously.
And when subscripts are
supported, we can also use this
function now to find the age of
the first attendee to the party.
Now, I want to talk to the rules
of appending key paths a bit.
When we append two key paths
together, it's like we're adding
them together.
But in order for this addition
to make sense, we really need to
look at the types of the key
paths involved.
Specifically, we need to look at
each base type and each property
type.
And the inner types need to
match.
And if that's the case, we can
form a key path from the
original base type to the final
property type.
And in this way, it's like key
paths actually don't care how
they get from the, their base
type to the property type, just
that they can, and the compiler
ensures that for us.
Now, I'd like to take another
look at another example or
another aspect of the type
safety that the Swift key paths
provide, in case it wasn't that
clear.
Suppose we wanted to output a
summary of our party.
We could form an array of labels
in key paths, but what would we
expect this array, partyPaths,
what would we expect the type of
it to be?
After all, theme is a string,
the attending is an array of
kids, and the celebrant is a
reference to a single kid.
In this case, we get a new type.
It's a, it's an array of partial
key paths to BirthdayParty.
And partial key paths are
partially type-erased key paths.
They know about their base type,
but they can point to any valid
key path for that particular
base.
And so in this case, let's go
ahead and print our report.
We'll zip our titles and paths
together, use the party path to
get the party value, and then
print our report.
And you can see Mia's having a
space-themed -- well, it looks
like a family birthday party --
but it's space themed, which
should be pretty fun.
Now, I want to add an extension
to BirthdayParty.
We're going to add a function
that lets our kids blow out
their birthday candles, and this
is going to be a little bit
different from what we were
doing previously because up
until now, we've been reading
key paths, and now I want to
write to a key path or use a key
path to write to a value.
So we're going to add our
functions, and I want to point
out that age key path is
actually a new type.
It's a writable key path of a,
that starts at a BirthdayParty
and goes to a double.
We can use writable key paths
just like regular key paths to
get values out of our values,
and we can also use them to
mutate our values.
And we can finally go ahead and
blow out our candles.
This all looks pretty good,
except for one problem.
Doesn't compile.
This is a birthday catastrophe.
So let's try to, I'm going to
debug this live on stage.
Debug. So the compiler's telling
us, "Cannot assign to an
immutable expression of type
'Double,'" which is very Swift
of it.
Let's see if we can figure out
what's going on.
It's saying immutable, but we
are passing a writable key path.
And we are indeed passing a key
path.
Let's confirm, though, that this
key path is actually to
immutable variable.
That, you know, sometimes you
say let when you meant to say
var.
So let's bring back the
declaration of kid, but we'll
see that var, our age is indeed
mutable.
It's a var, so that's not the
problem.
So maybe the problem's with the
actual write itself.
And that's, and we're using the
subscript.
And I'm on stage telling you
that subscripts work, so that's
not the problem.
So it must be something to do
self.
Well, what's self?
Self is an extension on
BirthdayParty, so now we're
going to need to bring back the
declaration of BirthdayParty.
Luckily, we had some room.
And we'll see that
BirthdayParty's a struct, and
structs are value types.
And so the compiler's actually
doing the right thing here.
It's not letting us mutate a
BirthdayParty because our key
path's anchored in a
BirthdayParty there.
And so one trick we could do,
right.
We look in our bag of Swift
tricks that we know, and we see
that, oh, OK, we can just add
mutating, and everything will
work.
But when you do that, you want
to stop and think, is this
really the right choice?
Because we're not really
modifying the key, or the
birthday party.
We're modifying the celebrant.
Birthday parties don't have ages
last I checked, and celebrant's
actually a class, a reference
type.
And so for this, we actually
have another kind of key path
that adds reference mutable
semantics to mutations, and it's
called a reference writable key
path.
And so let's go ahead and use
it.
And this compiles, and we can
finally assert that our little
boy, Benji, is finally one year
older, although I think he goes
by Ben now.
And so we can review the
difference between these two
kinds of mutating key paths.
So we have a writable key path.
And writable key paths write
directly into their value-type
bases.
So the base or the chained bases
need to be mutable.
Whereas a reference writable key
path simply invokes a property
setter on the reference type.
And all of these key path types
form an inheritance tree, each
more specific than the last.
Rooted at the top of this tree
is another kind of key path that
I haven't talked about called an
any key path, and this is a
fully type-erased key path.
And this is useful for when you
have key paths that are
comprised of multiple bases to
multiple different property
types, usually in a collection.
Now, if all of this seems a
little bit complicated, I assure
you that the rules for the kind
of key path that you want, and
use, and get are actually fairly
simple and match the rules that
you're already familiar with
from working with Swift value
types and reference types.
We'll start out with eliminating
half of the problem.
Read-only properties always
yield a key path.
With read-write properties,
things get a little more
nuanced.
Mutable value type bases or
chained mutable value type bases
will result in a writable key
path.
And so writable key paths help
let you write efficiently into a
value type.
However, if one of those value
types is made immutable, say
through a let statement, the
mutability of that property goes
away, just as it does when
you're using regular properties,
and you're left with just a key
path.
And saving the simplest case for
last, read-write properties on
reference type bases always
produce reference writable key
paths.
Now, I want to share one last
detail regarding the behavior of
key paths.
When we can use key paths with
subscripts, it's important to
know how their behavior differs
from closures.
Consider the following example.
Here I'm going to form a key
path to the first attendee of
the birthday party and use that
to identify their, use our
partyPersonAge method from
before to identify their age.
And not surprisingly, the key
path that we get here is
actually, is going to the zeroth
element of the attendees array.
Well, let's suppose I change the
index to 1.
I also care about the second
attendee's age for some reason.
You would be, might be surprised
that the resulting key path
actually is unchanged,
regardless of my changing of
index.
And in this way, key paths
became different than closures.
They capture by value, and so
when this feature becomes
available -- I wanted to say
this today so that you're not
surprised, and now I have.
So at this point, we've seen
many examples for how these
type-safe key paths satisfy our
goals for fast, type-safe and
expressive property traversal.
I'm going to change gears a
little bit now, though, because
I want to talk about how these
key paths can be used to improve
existing APIs in Swift today.
Specifically, I want to talk
about how we applied them to key
value observing.
As you probably know, KVO is
Cocoa's way of allowing objects
to establish relationships to be
notified about changes in their
state.
And if you've tried to use KVO
in Swift up to now, you probably
know that it leaves a little bit
to be desired.
Let's suppose we have a
reference--
[ Applause ]
Don't clap yet.
Clap in a minute.
[laughs] Let's suppose we have a
reference to an Objective-C
value -- say the little kid,
Mia, from earlier -- and we
really care about when this
kid's age changes.
We think that forming an
observation should simple, look
as simple as this.
Now clap. [laughs]
[ Applause ]
So I want to call out some
details about this form.
We are forming our observation
directly on the value type using
our new type-safe key paths.
What we get back is an
observation token similar to our
Notification Center APIs.
And this observation token is
doing two things for us.
One, it's saving us from having
to deal with unsafe raw pointers
with context to uniquely
identify our observation.
Now, our observation is directly
tied to our, the observation
that we get back.
Two, it's managing the life
[inaudible] of our observation.
And so if I, if -- well, in this
case, I can't set it to nil --
but if I were to set it to nil,
the observation would be toward
now [phonetic], which is a vast
improvement over getting
exceptions and having your app
crash when you forget to
unregister an observation.
Finally -- and this is perhaps
the nicest part -- you now
handle your observation's
reaction with a closure as
opposed to nested if statements
looking at and comparing
strings.
Let's take a look at the
parameters to this closure a
little bit more.
It has two parameters.
The first is the observed object
itself.
This is the same reference, Mia,
but we provide it as a parameter
to help you avoid accidentally
creating retain cycles.
Second, parameter is a change
object, and this is very similar
to the existing KVO API, except
you, if you've used, if you're
familiar with that API, you know
that that's a loosely type
dictionary, and here we're
actually providing a strongly
typed struct.
So we know observed is a kid
because of the key path and we
know that the age that's
changing is a double because of
the key path.
And now, I want to go ahead and
make this real.
So we'll do an example.
Let's suppose we have a
controller that cares about when
our kids get older.
It's the KindergartenController,
and it has a single key value
observable property, its
representedKid.
And we're going to form an
observation, so let's go ahead
and add an i var for our
observation.
And then, we'll form that
observation right now to our
controller's representedKid's
age, and we'll hold onto that in
the i var.
And we'll add our super-secret
business logic.
And if you're looking at this
and thinking that it's wrong, I
assure you that once you're
ready for kindergarten, you're
always ready for kindergarten,
so it's actually correct.
And that's it.
That's the entire declaration of
our controller.
There is no need for a dnit
[phonetic] where I throw away or
tear down my observation because
it's tied to the life
[inaudible] of that observation
token.
And so when the controller goes
away, the observation token will
go away, and it fits on the
slide.
So let's go ahead and create our
controller.
And here we'll point to Mia, and
we'll have Mia blow out her
candles with our function that
we defined earlier.
And we'll finally see through
the power of these new type-safe
key paths our little girl grow
one year older.
Now, at this point, I've shown
everything that I'm planning on
showing, but I do want to circle
back and talk about string key
paths one more time.
These guys are going to continue
to exist, and they're going to
remain useful for legacy APIs
that, you know, insist on using
strings.
However, starting in Swift 4,
you now have access to these new
type-safe performant key paths,
and we brought them to the
language because we feel that
they are so incredibly
important.
And they're only going to grow
to be more important over time.
And with that, I'd like to bring
Tony back on stage to discuss
another important language
feature that we're bringing to
Swift this year.
Thank you.
[ Applause ]
>> All right.
Thanks, Michael.
So next up, we're going to talk
about encoding and decoding.
So broadly speaking, encoding
and decoding is about the
conversion between your native
and custom Swift data structures
and archived formats, especially
JSON.
Now, many of you have told us
about the challenge of a
mismatch that is between the
strongly typed language of Swift
and loosely typed archive data
formats like JSON.
We believe that the answer to
this challenge is something that
starts with the language itself
and also takes advantage of the
compiler, the standard library,
and Foundation to make
interaction with JSON simple,
but also to provide you the
opportunity for powerful
customization.
So let's get started by looking
at an example.
Here is some JSON from one of
our favorite sites, GitHub.
This is the result of asking for
information about a commit made
to a repository, and it's pretty
standard JSON.
It's a JSON object or maybe what
we would call a dictionary.
It supports and arbitrary number
of key value pairs -- in this
case, name, which is a string;
email, which is also a string;
and date, which is a string.
And the reason is because, of
course, JSON has no native type
for dates.
But there are many conventions
by which dates are encoded in a
way into JSON, and this one
appears to be iso8601.
If we were to represent this
JSON in Swift, it would look
very different.
For example, we would make a
strong type for it, a struct
perhaps called Author.
This struct would have exactly
three properties -- name and
email, which are strings still;
but date, as you can see here,
is using Foundation's date type.
And the reason that's important
is because, as you interact with
the rest of the Cocoa SDK and
other APIs, you'll find that
date is the kind of type used to
represent a point in time.
And so this is where we've
reached that challenge, right.
How do we convert between that
loosely typed JSON on top and
the strong Swift type on the
bottom?
Well, we think it should be as
easy as this.
Simply adopt a protocol on your
struct and let the compiler, the
standard library, and Foundation
do the vast majority of the work
for you.
Thank you.
[ Applause ]
So let's turn this slide into
some real code.
First, I'm going to turn that
JSON into a string using Swift
4's cool, new string literal
syntax, the triple quote, and
then turn that string into data
using UTF-8 encoding, which is
pretty common for JSON.
Struct Author remains the same,
of course.
Next, we create a decoder.
This is what is actually doing
the conversion between the JSON
and our Swift structure.
We tell the decoder about that
convention, the iso8601 date.
And we'll talk more about this
later.
And finally, we ask the decoder
to decode an author.
The result is not an any.
It's not a dictionary.
You don't have to fish through
strings or check for keys.
It's already the type that you
care about using, in this case.
So that was pretty easy.
Let's bump up the difficulty
level one notch.
This JSON is actually part of a
larger set of JSON that comes as
the result of this request,
which includes things like URLs,
and additional strings, and
integer values.
So in Swift, we can just follow
suit.
So I'm going to nest my struct
Author in a new struct called
Commit, which is also codable.
There you can see that I'm able
to use Foundation's URL type and
our struct Author.
So you can see how we can
recursively descend into types,
if they conform with codable, to
decode them as well.
The string, which is, message,
which is a string, and our
comment count property.
And to decode this, again, one
line of code.
We're going to decode a commit
this time.
And the result: Our strong Swift
type, which lets us use the
Swift language features that we
know and love to get at the
values that we care about in the
archive.
In this case, it's simply
property access.
So let's look at what's going on
here.
First, the codable protocol,
which is actually not one
protocol, but two.
The first is called encodable
and has one function, encode to
encoder.
The purpose of that function is
to allow the type to tell the
encoder all of the information
that it needs in order to
recreate itself at a later time.
The corresponding protocol,
decodable, has one initializer.
The purpose of the initializer
is to allow the type to get the
values that it needs from the
decoder and then use those to
create a fully initialized
instance of itself that is ready
for use.
The primary design point of
these APIs is to use a Swift
behavior that you may already be
familiar with, and that's called
protocol extensions.
So in Swift, protocols can not
only define an interface, but
via an extension, they can
provide a default implementation
for that interface.
And they let you write your own
implementation for either part
or whole of that protocol to
customize the behavior.
So let's go back to our commit
to see how this works.
When I adopted the codable
protocol, the compiler actually
generated an implementation of
encode to encoder and initfrom
decoder for us completely for
free.
And in this case, I don't need
to customize anything about
them, so I can just omit them
completely from my type.
There is one thing I do want to
customize about this type,
though, and that is the name of
this property.
Now, you may notice that it is
using snake case, which is
pretty common in JSON, but it
doesn't match Swift's naming
conventions.
So let me show you how we're
going to fix that.
First, there's one more thing
that the compiler generated for
us, and that is this private
enum called CodingKeys.
This enum is backed by a string
and adopts a CodingKey protocol,
which, again, we'll talk more
about later.
But what's interesting to note
here is that this enum has four
case statements that match the
names of my four properties.
And so in order to customize the
name of my property, I just need
to customize the name of my case
statement.
So to do that, I'm going to
change this comment count snake
case to camel case.
But as you can see, I remain
compatible with the JSON that
we're reading by setting the
string value of that case to be
the value we expect to find in
our archive.
Now, if that's all the
customization that we needed to
do, then we're done.
Maybe you can stop watching now
and leave, but by the end of the
talk, I do want to show you how
we can do even more kinds of
customizations on this commit.
For now, I'd like to hand it
over to my colleague Itai to
show us a demo of this stuff in
action.
[ Applause ]
>> Thanks, Tony.
So Tony showed you just how easy
it is to adopt codable in your
types, but let's dive in to see
what this might look like for
many of your apps in practice.
I've got a small app here that
I've been prototyping lately.
Because I'm such a big fan of
Swift, I like to watch for
interesting git commits as they
come in on Swift's GitHub repo.
I've written a small app here
that talks to GitHub's JSON REST
API to parse these commits and
show me them in the table view.
So let's take a quick look at
how easy it was to put this app
together using the new codable
APIs.
If we switch to Xcode, you'll
notice some of the same models
that Tony had up on his slide
expanded a little bit.
We've got the same commit info,
author info, and we've gone
ahead and done the same
renaming.
On the right is the JSON spec
that GitHub provides, but with
some of the irrelevant parts
snipped out.
And if you'll notice on the
bottom right, we've got some
info in the JSON spec that we're
not currently decoding.
That's actually OK because it'll
get ignored by default, and so
we can come back to this later.
So let's hide this JSON spec and
go a little bit further down
into our file to see how we can
use these models in practice.
So in here, we've got our
CommitsViewController, and this
is the view controller that
actually displays these commits
in our table view.
And so the view controller here
has our table view, along with
an array of these commits.
And note here that this is an
array of our type.
It's not an array of any or
anything similar.
When we're going to go and
display this data, we can fetch
the data from GitHub, and then
using a JSON decoder, just like
Tony showed you, we can go ahead
and request to decode an array
of these commits into our type.
Once that's done, we can reload
our table view and have that
display.
Now, if anything goes wrong, we
can catch that error and display
some localized information to
the user to tell them what went
wrong at a high level.
Now, this is how you load the
data into your app, but let's
take a look at how this hooks up
to our UI.
So a little bit further down in
the file here, I've got a helper
method that lets me set up table
view cells right before they're
displayed to the user.
So in here, to set up my custom
table view cell, I'm going to
pull out a commit from our array
of commits, and then using the
strongly typed properties on
that commit, we can hook it up
to our UI.
And note again that we're not
downcasting from any and we're
not peering through arrays or
dictionaries.
This is our type the way we
wrote it and how we want to use
it.
Now, that's all nice and good,
but let's go back into our app
and you'll take a look here and
notice that we've got some room
in the UI that I've left here
for hooking up the hash values
of each of these commits.
But it's not hooked up yet, so
let's go ahead and do that now.
If we go back to our models and
pop open the JSON spec again, we
can see that there's a hash
property in the JSON spec that
we haven't been requesting.
So let's go ahead and add that
to our type.
And then, if I go ahead and
build our project to use it,
you'll notice that I actually
got a build failure.
So let's explore why that
happens.
As part of this type, I've
created a custom CodingKeys
enum.
Now, the CodingKeys enum that
you put in your type is a really
powerful tool for controlling
what the compiler generates as
part of init from NNCode
[phonetic] 2.
In this case, I provided a
CodingKeys enum that renames my
info property to commit to match
what's in the JSON.
But in this case, the hash
property that I just added isn't
found in the CodingKeys.
Now, what the compiler will try
to do is if you purposefully
leave a property out of your
CodingKeys enum, it'll actually
omit it from your encoded and
decoded representation.
But what's happening here is
that because this hash property
doesn't have a default value, if
the compiler were to try and
generate an initializer for us,
there'd be no reasonable value
to initialize this property to,
and so the compiler refuses to
do it and we get our build
failure because our type
actually doesn't conform to
decodable.
Now, in this case, we actually
don't want to leave this hash
property out of our encoded
representation.
We do want to decode it.
So let's go ahead and mirror
that same property in our
CodingKeys enum.
Let's hide the JSON spec again
and go and hook up this property
directly to our UI.
So here in the cell setup method
that we have, we're going to add
another line of code that grabs
that hash, and here we're going
to shorten it up a bit so it
fits nicely in our UI.
And just like using everything
else, using the strongly typed
properties, we can hook it up to
our UI directly.
So let's go ahead and rerun our
app and take a look to make sure
that things hooked up correctly.
Here, now that we've rerun, we
can actually see that everything
is hooked up to our UI, and I'm
pretty happy because that took a
whole of four lines of code to
add to our app.
[ Applause ]
Now, going back to the code for
a moment, let's take a look at
what we can do when things go
wrong in our app.
So if we pop open the JSON spec
one more time, you might notice
that in the bottom right, we
have one final property that we
haven't been decoding, so let's
go ahead and try to do that now.
We're going to add a URL
property to our type, and again,
we're going to want to mirror it
in the CodingKeys enum.
This time, though, let's give
the CodingKey a value that is
clearly not found within our
JSON payload.
Now, when we go ahead and try to
decode this value, it won't be
found, and so this'll actually
be an error at decode time.
To see how we can handle that
error, let's hide the JSON spec
again and go down to where we
perform the decode.
In order to handle this error,
we can catch a decoding error,
key not found error, which
indicates that we tried to
access something with this key,
but it wasn't found in the
payload anywhere.
Along with that, we get some
contextual information about
what went wrong and where.
So now, let's set a break point
here, and run our app with this
faulty key, and take a look to
make sure that we can catch this
error and we hit the break
point.
And so now that we run this app,
you'll notice that we do hit
that break point.
Now, if we go ahead and print
the key, you might see that this
is in fact the URL key that we
gave a faulty value to, and in
fact, it wasn't found in the
payload, and so we get the
error.
Now, here if we take a look at
the contextual information, you
might see two useful features
that helps you debug what
happened.
First, a debug description for
you the developer to figure out
what went wrong along with the
coding path that describes where
in the payload something
happened to cause this to go
wrong.
Now, this is all nice and good,
but in fact, if my URL is
something I don't really care
about all that much and I might
not need it, one way to handle
this error is to make your URL
optional.
When you make the property
optional by default, if the key
or value is not found, it'll
actually get set to nil on
initialization.
So let's hide our UI a bit and
rerun our app to see if we hit
that break point or not.
And in fact, when we rerun it,
we don't hit that break point
because the value is set to nil
by default, which is a handy
behavior.
Now, let's go back to our code
and take a look and see what
other errors we might be able to
catch that are helpful.
One other error like that is the
DecodingError.valueNotFound,
which indicates we tried to
decode something of this type
but in fact found nil.
Again, you get that same
contextual information that
tells you what went wrong and
where.
Along with that, you might want
to catch a type mismatch, which
indicates that you try to decode
something of this type, but
something else was found in a,
in the payload.
Say, you tried to decode a
strong, but instead, a number
was found.
And again, you get that same
contextual information as
before.
Now, these errors are really
handy for when you want to debug
when something goes wrong, but
in the general case, you don't
really want to catch these at
the top level like here.
Instead, you just want to
capture general error and
display something localized to
the users so they can figure out
what went wrong or report the
bug.
Now, these are actually a very
powerful tool to do some more
advanced things.
If you customize your init from
or encode to, you can actually
catch these errors within your
types to do powerful things like
data migrations, renaming
properties, and so on and so
forth.
But within our app, we actually
don't need that because I've got
exactly what I want with not
much code.
And so I'm going to turn things
back over to Tony to talk about
some of these more advanced
encoding and decoding topics.
Thanks.
[ Applause ]
>> Thanks, Itai.
All right, let's move on to talk
about some more advanced topics
in this, with encoding and
decoding.
And to do that, we're going to
go over what I call the three
pillars of our codable API
design philosophy.
The first is that we really
wanted error handling to be
built right in, as you just saw
in this demo.
So when you're working with
archived data, dealing with
unexpected input is not a
question of if, but simply when.
This can be data corruption, it
could be unexpected API changes
from where you receive that
data, or even something like
malicious input -- somebody
trying to probe for weaknesses
in your app.
And so we decided that there
should be no fatal errors as a
result of parsing untrusted
data.
However, we do use the fatal
error in Swift if we detect
something that may be a
developer mistake.
And in those cases, there's a
string with the fatal error
that'll tell you where you may
have gone wrong.
For everything else, we use
Swift's built-in error handling
mechanism, and those kinds of
errors are possible on both
encoding and decoding.
So let's look at what they are.
First, there's encoding.
So there's only one kind of
error on encoding, and that is
an invalid value.
So for some kinds of formats
like JSON, we wanted to give
them the flexibility to handle
input that they may not expect
without resorting to a fatal
error or some kind of default
value.
In JSON, for example, not a
number or infinity are not valid
values.
And so in those cases, they can
throw in error.
There may not be much you can do
about this at the type, by type
level, but you could still catch
it at the top level and present
an error to your user or prevent
some other kind of recovery
mechanism.
On decoding, there are four
kinds of error, three of which
we just saw in the demo -- type
mismatch, missing key, and
missing value, which you can
handle, again, either by using
the air handling mechanism if
the [inaudible] a required part
of your type or by making those
properties optional.
And lastly, we have data
corrupt.
Data corrupt is our kind of
catch-all error for all the
other kinds of things that can
happen during a decode.
And to see where it might be
useful, let's go into some depth
on what actually happens during
a decode.
First, in the beginning, all we
have are bytes.
It could be from the network.
It could be from a file on disk
or somewhere else in your app.
But regardless, at this point,
we don't really know anything
about them.
And so the very first step is to
convert those bytes into
structured bytes.
For example, the JSON decoder
has to verify certain
requirements of the JSON spec
are met -- the particular bytes
at the beginning of the archive,
which indicate string encoding;
certain characters which are
used as delimiters for strings,
numeric values, arrays, and
dictionaries, and so forth.
If any of those look wrong, then
the JSON decoder can throw an
error and stop the decode right
there.
After that, we want to convert
from things like JSON arrays,
and dictionaries, and strings
into your types, commits and
authors.
That is, after all, the entire
point of this API.
But there may be more that we
can do, and we call that
domain-specific validation.
For example, let's say that you
have a type that has an integer
property, but the only valid
values for the integer are
between zero and 100.
Or maybe your type has two
Boolean properties, but they
have to have an exclusive or
relationship with each other.
These kinds of things can be
difficult to express in Swift's
type system, but we do think we
have a great tool for handling
those, and that's just simply
writing more Swift code.
And so we wanted to make sure
that we provided you the
opportunity to do that if you
have those kinds of
requirements.
Finally, you may have
graph-level validation.
This is about the relationship
of the objects in the graph to
each other or to another part of
your app.
Let's apply this to our commit.
So earlier we saw how we
customized our commentCount
property by customizing the enum
called CodingKeys.
Now, we're going to customize
the decodable by implementing
init from decoder.
First, I asked the decoder for a
container.
Containers are what match up
your keys to the values that you
expect to find in the archive.
Once we have a container, we can
ask it for the values that we
need.
So in this case, a URL, a
string, our author, and there's
our recursive descent again, and
the integer value for
commentCount.
Now, let's say that I have an
additional requirement that I
need to verify as part of my
spec, and that is that all URLs
have to be HTTPS.
If they're not, then something
has gone wrong.
So let's see how we might do
that.
First, make some more room on
the slide.
After that, I'm going to use the
URL API that we already know how
to use, and that is the scheme
property.
Here I can just check that it's
equal to HTTPS, and if it's not,
I can throw one of these
decoding errors, providing a
debug description so that you
can catch it in your debugger as
Itai showed in the demo.
Now, what you see, what you'll
notice is not here is this type,
commit, looking into the string
value for the URL.
We can allow URL to decode
itself, and that's part of
what's so great about this
design.
So URL knows if that string is
URL or not, and if it's not,
it'll throw an error before we
even get to this point.
And because of the design of
Swift's error handling, that can
propagate out of this type to
the one that's decoding the
commit or even to the top level.
Let's move on to the second
pillar, and that is
encapsulation of the encoding
details.
We felt it was really important
to make sure that the keys and
values that a type chooses to
put into the archive are private
to that type.
And the reason it's important is
because that frees us from
something that, from designing
something that has global
knowledge of everything in the
archive that can possibly be
there.
The main mechanism we have for
performing this encapsulation is
called containers, and we have
three kinds.
The first is a keyed container.
Keyed containers are the
preferred choice in the vast
majority of cases, and the
reason is because they're by far
the most forward and backward
compatible.
If in a new version of your app
you have new or changed data,
you simply have to use a new
key.
This makes the most versions of
your app compatible with the
most versions of your data,
which is the best possible
outcome for everybody.
Let's look at what those keys
actually are.
So earlier we talked about the
CodingKey protocol.
Here's what it is.
It has two protocol, or two
properties and two initializers.
So the properties are
stringValue, which is handy when
you're working with JSON, for
example, but you can also
provide an integer value, which
is useful for formats that may
support a more efficient encoded
binary representation.
The initializers, what I would
like you to notice is that they
are optional.
What that means is that the
decoder has an ability to
perform an additional level of
safety checking.
It can verify with your coding
key that the value that's found
in the archive is actually what
you expect to find there.
Typically, you're going to adopt
this protocol on an enumeration,
like the one we've seen so far.
And again, what's happening here
is that the compiler in the
standard library are providing
an implementation of all four of
those requirements for us
completely for free.
So in this case, because the
enum is backed by a string, the
compiler uses the case name as
the string value, both for the
property and for the
initializer.
The intValue, though, remains
nil because there's not enough
information in this enum to
assign a particular value to
that.
Earlier when we customized the
case name, you can see how that
worked now.
We changed the name of the case,
but the value remained the same.
And so stringValue remained
compatible with our GitHub API.
If you're writing library code,
I would encourage you to
consider backing your coding
keys with an integer.
If you do this, you still get
more implementation for free
from the compiler -- in this
case, an integer value, which,
again, could be useful for
formats that may support integer
keys.
We also support unkeyed
containers.
These encode and decode in
order.
Use these for ordered or
unbounded data, and, you know,
the reason for that is that, of
course, you don't have to
generate fake keys in order to
get your data into an archive.
We also support single value
container, which, as the name
suggests, holds exactly one
entry.
Use these for primitive types.
For example, date stores the
number of seconds since a
reference point in time.
Now, when you choose these, just
be aware that they are the least
compatible choice, so keep that
in mind.
Let's return one more time to
our commit.
So we saw how we customized the
commentCount and the CodingKeys
with that, the decoding by
changing init from decoder.
Now, let's look at encoding with
encode to encoder.
And actually, I don't need to
customize anything here, but I
still want to show you what it
looks like so you can understand
how it works.
So first, we get a container,
and that container, as you can
see here, is keyed by our own
private-to-us CodingKeys.
That container is how I can
encode the values that I want to
be put in the archive -- our
URL, message, author, the
recursive descent again, and the
comment count.
I do want to show you an example
of where you may choose a
different kind of container, so
let's say that we are working
with the GO JSON spec, which has
this concept of a point.
And point has two values, and it
should be an array of two
numeric entries in JSON.
So in order to make that work,
I'm going to adopt encodable and
implement the encode to encoder
to use a unkeyed container.
And you'll notice, of course,
there's no key argument to this
container.
And when I encode, I use no
keys.
And the result looks something
like this in JSON.
We also support nested
containers.
So let's say that maybe the
second entry in my dictionary
actually needed to be an array
of three values.
So we support nesting unkeyed
containers and keyed containers,
as you see here, or any other
combination of keyed, unkeyed,
and single value.
The primary use case for nested
containers is actually classes.
We've talked a lot about structs
so far, but nested containers
gives us a natural mechanism for
encapsulating our superclass
data from our own data as a
subclass, which is a change from
NSCoding.
Let's look at an example.
Here's everyone's favorite
object-oriented example,
animals.
So animals have a leg count,
naturally.
And its own coding keys.
And here you can see that on
this class, when I implement
init from decoder, it is a
required initializer.
Here I create a keyed contained
using the animal's coding keys
and decode my leg count.
Pretty similar to what we've
seen so far.
Now, let's subclass it.
Dogs is a kind of animal that
has a best friend, which is the
kid from our birthday party
earlier.
Now, you notice that dog also
has a private enum called
CodingKeys, and yet, even though
it has the same name as the one
from the superclass, because
it's private, it doesn't
conflict with the one that
animal uses.
So when I implement the dog's
init from decoder and get a
container with its own coding
keys, I can decode it in a
type-safe way with the keys that
are important to it, not its
superclass.
Now, we do need to finish that
nesting.
So we could call superclass, our
superclasses init from decoder
with the decoder that we
received.
However, that doesn't give the
container a chance to nest that
superclass data.
So the easiest way to do that is
to use this convenience API --
it's called superDecoder -- that
gets a new decoder that we could
pass to our superclass.
And by calling super, we finish
satisfying Swift's rules for
creating an initializer that
results in a finally initialized
type that's ready for use.
Finally, we have our third
pillar, and that is abstracting
the encoded format from these
types.
We felt it was important to be
able to reuse one implementation
of these protocols.
We didn't want to wind up in a
situation where we had many
almost duplicated
implementations of encodable and
decodable to support new
formats.
So by abstracting the format, we
can allow brand-new formats
without any library changes.
Those formats can come from us,
or from you, or even from Swift
Packages, and those formats can
work with types that come from
us, or from you, or with Swift
Packages.
We do understand, though, that
different formats have different
fundamental types and different
conventions.
So the mechanism we have for
working with that is called
encoding strategies.
These are encoder- and
decoder-specific customizations
for certain types.
For example, in JSON, we saw one
already for date.
In our GitHub example, the date
was encoded as an iso8601
string.
But there are other conventions
that are possible.
For example, the number of
seconds since a reference date,
the number of milliseconds since
a reference date, or you can
even specify a completely custom
date formatter if you have
something very specialized in
mind.
The JSON encoder and decoder
support other kinds of
strategies.
For example, for data.
In JSON, it's very common to
Base64 encode your data.
But we also allow you to
customize this by choosing a
strategy that encodes it as an
array of bytes, or you can
specify something completely
custom, like this one, which
turns all zeros into sheep and
everything else into a dog.
I don't know why you'd do this,
but it's possible, so there you
go.
Now, this abstraction helps us
with different formats as well.
So we've seen, we've talked a
lot about JSON today, but
actually, we are also
introducing a property list
encoder and decoder.
And property lists, unlike JSON,
have native types for data and
for date.
And so when these encoders and
decoders encounter these objects
either in the object graph that
it's encoding or in the data
that it's unarchiving, we can
convert them into the right
types that are proper for that
format.
Because of these abstractions,
we're able to adopt the codable
protocols on a wide variety of
Foundation types, including all
the ones you see here.
Now, we've talked about a lot of
codable API.
I want to give you a visual
overview to help you understand
what the big picture is.
So we're going to start, of
course, with your type.
Your type adopts two protocols.
They're called encodable and
decodable.
These have a function and an
initializer, which give you
access to an encoder and a
decoder.
These provide you access to
containers, and that's what
actually holds the values that
are in the archive.
In the case of a keyed
container, we use the coding
keys that are defined by your
type.
And finally, the encoders and
containers provide an
abstraction for encoded formats
like JSON, property list, and
more.
All right, so we started today
by going over some new API and
improved performance in this
year's release of Foundation.
After that, we looked at the new
strongly typed key paths for
Swift, including one really cool
use case, our brand-new,
closure-based KVO API.
And finally, we went over the
new codable protocols, which
make integration with formats
like JSON easy, but also allow
you the opportunity for powerful
customization.
For more information, check out
this link.
We have a couple of related
sessions that we've talked
about.
Thank you so much.
[ Applause ]