Transcript
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Music ]
[ Applause ]
>> Good afternoon and
welcome to session 226,
What's New in CloudKit.
My name is Paul Seligman.
I'm an engineer on the CloudKit
client team and I'm very excited
to be with you here today
to talk about some updates
and new features in
the CloudKit ecosystem.
So, what are we going
to talk about today?
We're going to start off today
with a quick overview
of what is CloudKit.
We're then going to switch
gears and talk about Telemetry,
a new feature which
gives you the ability
to visualize how your
CloudKit-backed applications
are behaving.
We're going to talk about
some improvements to our APIs
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
We're going to talk about
some improvements to our APIs
and their availabilities.
And we're going to talk
about sharing, a new feature
which gives your
users the ability
to share their data while
maintaining full control
over who has access to it.
So, what is CloudKit?
CloudKit is a technology
that gives you the ability
to have your application data
and your user data available
wherever you need it.
CloudKit is a framework
which gives you access
to a database stored on iCloud.
We use CloudKit extensively
inside of Apple.
This gives you the confidence to
know that we are committed to it
and it gives us the confidence
to know that we can scale
to hundreds of millions
of users.
CloudKit is available on
all of Apple's platforms.
Now, I'm going to quickly
summarize an introduction
to CloudKit, covering topics
that we did a few years ago
in introduction to CloudKit.
I recommend you go check out
that session after this one
if you'd like a broader
introduction to the ecosystem.
I'd also like to mention these
talks which go into more detail
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
I'd also like to mention these
talks which go into more detail
about specific aspects
of CloudKit.
You can use these to
find ways that you
and your application can use
CloudKit to your advantage.
Now, all the sessions
are online and are linked
from
developer.apple.com/CloudKit.
Here we see the list of objects
that every developer
using CloudKit needs
to be familiar with.
Let's step through them
starting with Containers.
A Container is the mechanism by
which we silo data up on iCloud.
So, notes uses a Container.
Photos uses a Container and your
Applicationm when built on top
of CloudKit, will also have
access to its own Container.
If we look inside
of a Container,
we see that Containers
contains databases.
And until last week
this is our data model.
A Container had two databases,
the public and the private.
With the introduction
of Sharing,
we introduced a third database
type, the Shared database.
More about that in a little bit.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
The basic unit of storage
inside of CloudKit is a record.
A record is a group of key value
pairs and typically it maps
to an object model, an
object in your data model.
Now, we don't store records
loosely inside of databases.
Rather, we encapsulate
records inside of Record Zones.
Many records can exist
inside of a record zone
and many Record Zones can
exist inside of a database.
Different databases support
different types of Record Zones.
The public and private databases
have a default record zone.
This is where all your
records are going to end
up unless you specify otherwise.
Your private database can also
contain custom Record Zones
which are zones that
your application creates
and uploads into the database.
And lastly, the new
shared database consists
of shared Record Zones.
With the introduction
of sharing,
we're going to add one new
core concept to this list.
The concept of a Share.
A Share is a subclass
of a Record and as such,
lives alongside Records
inside of Record Zones.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
lives alongside Records
inside of Record Zones.
You can think of a Record as
being the thing that you want
to share and Share representing
how you're going to share it,
things like participants
and permissions.
Again, we'll get more
into that in a little bit.
Just know that it exists.
Now, I mentioned that we use
CloudKit extensively inside
of Apple and I wanted
to take a moment
to highlight some
of our clients.
In the public database
are a couple applications
that you've probably used,
the WWDC App and the News App.
The News App in particular
stores article content, images,
etc., in the public database.
And it's a great use
of the public database.
Storing content that you
want generally accessible
to all of your users.
And we can contrast this
with the private database.
The private database
is where you're going
to store the user's
private data.
We have several clients
of this inside
of Apple including iCloud
Backup, iCloud Drive,
iCloud Photo Library and Notes.
And I'm happy to report
that two new features,
Notes Collaboration and
Activity Sharing are both built
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Notes Collaboration and
Activity Sharing are both built
on top of CloudKit Sharing.
So, as such, the Notes
and Activity Applications are
clients of the shared database.
Two years ago we
introduced CloudKit
by providing two
native frameworks,
one on iOS and one on macOS.
Last year we extended that
family, adding a data framework
for tvOS and two web
frameworks, CloudKit JS
and CloudKit Web Services.
The web frameworks give your
users access to CloudKit data,
whether they're on the
web or on a platform
that doesn't have a native
framework alternative.
And this year we're going to go
ahead and complete the circle
by adding a native
framework on watchOS.
With this, we now have a native
CloudKit framework available
across all of Apple's platforms.
Let's take a moment and step
through some notable
platform-specific changes
this year.
Starting with macOS.
The big news that we
want to share with you is
that you no longer need
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that you no longer need
to distribute your application
via the Mac App Store in order
to take advantage of CloudKit.
[ Applause ]
Using the new iCloud for
Developer ID feature,
you can directly entitle your
application to use CloudKit
and other iCloud services via
your permissioning profiles.
Next I want to talk
about server to server.
This is a feature that's
been out in the wild
for a few months now and it's
the, gives you the ability
to have your servers directly
talk to CloudKit servers
as administrative users.
Your servers can
authenticate themselves
to CloudKit using a
public/private key pair you've
previously established on
the CloudKit Dashboard.
And you can set your servers
to have full rewrite access
to the public database.
This is a great way for
you to import your data
from your servers into
CloudKit or to export data
from CloudKit to your servers.
Or to keep two sets
of data up-to-date
between your servers
and CloudKit.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
With the introduction of
CloudKit as a native framework
on watchOS, you now
have another mechanism
to keep your watch Apps and
your iOS Apps up-to-date.
In that way, you can think
of CloudKit as an alternative
to the watch connectivity
framework.
And CloudKit comes with
one notable advantage.
That is standalone
functionality.
CloudKit uses NSURL
session and as a result,
we're going to send our network
over the best available
interface.
If your watch is
connected to an iOS device,
we'll send traffic
over that iOS device.
But the watch is also
capable of talking directly
to CloudKit servers
when it's on Wi-Fi.
Now, we are presenting
a full-ish version
of the CloudKit API.
With the introduction of
CloudKit as the native framework
on watchOS, you now
have the ability
to write similar application
code that uses CloudKit
across all of Apple's platforms.
The Activity App for
example has used this
to write similar CloudKit
code on iOS and on watchOS
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to write similar CloudKit
code on iOS and on watchOS
to provide the activity
sharing future.
Now, notice I said similar
code and not identical code.
As you write code and
deploy it to the variety
of Apple's platforms,
you need to keep in mind
that the strengths and
realities of each platform.
In other words, you're going
to hit limited resources
in some cases.
You need to keep in mind
the CPU characteristics,
the storage capacities and
the network characteristics
such as latency and throughput.
You can use this in determining
how often you want to talk
to the servers and how
much data you're willing
to send over the wire.
As always, testing
is the best way
to tune your App appropriately
for the platform and ensure
that your users are going
to have the best
possible experience.
Now I'd like to change gears
and talk about Telemetry.
Telemetry is a new
feature which allows you
to visualize the behavior
of your CloudKit-backed
applications.
We surface Telemetry as a series
of charts which are available
on the CloudKit Dashboard.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
You can use these charts
to visualize your behavior
in the public database or an
aggregate of your behavior
across all users'
private databases.
You can scope these charts
so that you're viewing data
on an hourly, daily,
weekly or monthly basis.
And you can choose to view the
entirety of your application
or scope these charts down
to a specific operation type.
So, let's go and see
what this looks like.
Here we have the
CloudKit Dashboard
which you're probably
familiar with.
And I want to call your
attention to this new UI element
in the lower left,
the Performance tab.
When you select the Performance
tab, you now have access
to a series of charts
which gives you information
about how your clients
are behaving.
They fall into two categories.
The first is performance charts
and here we surface information
such as the number of
operations per second
and the average size
of your requests.
And again, you can scope this
so that you're visualizing data
in the public or private
database along a variety
of timescales and potentially
on a per-operation type basis.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of timescales and potentially
on a per-operation type basis.
The other type of chart that
we surface is what we call our
Correctness chart.
And the one I want to call
attention to is client errors.
This tells you what percentage
of requests that you have issued
that have resulted
in a client error.
Now, a client error is
a subset of the errors
that you might receive
from a CKOperation.
And it is that subset which we
think your application should be
able to resolve and
take action on.
So for example, maybe you
tried to save a record
and there was a conflicting
record change upon the server.
Or perhaps you attempted to
fetch changes from a Record Zone
that the server doesn't
know about.
Both of these would be
considered client errors
and would be surfaced
in this chart.
By being able to
visualize your error trends,
we hope that you can
take advantages to find
when your client's, situations
when your clients are seeing
abnormally frequent number
of errors.
Now, we've said in the past
that error handling is essential
for a CloudKit-backed
Application.
The difference between
an Application
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
The difference between
an Application
that handles errors
well and an application
that handles errors
poorly is the difference
between a functioning App
and a nonfunctioning App.
It's that serious.
It's an integral part in writing
a CloudKit-based Application.
So, we hope that you can
use these charts to figure
out situations in which you need
to go examine how your clients
are handling their errors.
For more information on
how to handle errors well,
I want to invite you
to tomorrow's talk,
CloudKit Best Practices.
We'll spend some time diving
into proper error handling.
Next, I'd like to talk to
you about some improvements
to our APIs, all of which
are new since the last WWDC.
And really, there's four that I
want to call your attention to.
Starting with Long-Lived
Operations.
Long-Lived Operations
give you a mechanism
by which you don't
have to repeat work
that you've already
done gets the server.
So, as it stands now, when your
application goes away, it exits.
Any operations that
were outstanding
on behalf of it are torn down.
Even if that operation was
moments away from completing.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Even if that operation was
moments away from completing.
By making your operations
long-lived,
your operations can outlive the
lifetime of your Application.
They will continue running
and CloudKit will continue
to cache responses from the
server in a local cache.
When your Application
is next launched,
you've resumed the operation.
And we're just going to go
ahead and feed you those caches
out of our local cache.
In many cases, this can
completely eliminate the need
for another network round trip.
We're going to talk about
Long-Lived Operations
in more detail at tomorrow's
talk, Best Practices, 9 am,
I hope you can join us.
Next I want to touch on
a topic that we've heard
of from our developers
and it has to do
with CKOperation
behavior on bad network.
And the picture I want to paint
for you here is we've
got a device.
The device has network
that says it's available
but we're not getting
any traffic going
over in either direction.
And as a side note, you
can actually go ahead
and replicate the scenario
yourself using the network link
conditioner, a great
developer tool
for replicating behavior
such as this.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
for replicating behavior
such as this.
Now, a CKOperation is a
subclass of an NS operation.
And as such, as a
QualityOfService property.
If your operation is
marked as user interactive
or user initiated, then on a bad
network, we're going to tear it
down after 1 minute and give
you a network timeout error.
If your operation has any of
these other QualityOfServices,
we're going to go ahead
and continue attempting
it for up to seven days.
It might not be what
you expected.
What's more, if you choose not
to set an explicit
QualityOfService
on your CKOperation, we
will choose one for you
and we choose utility.
So, if you add all
this up, we get a lot
of developer reports saying,
you know, it's been 5 days,
why is my operation
still outstanding.
So, we want to address
this and we're going
to address this with
two new APIs.
The first covers network
inactivity and we expose it
as the timeout interval
for request property
on a CKOperation.
It defaults to 1 minute
and it's the amount of time
that we're willing to wait for
a packet to go over the wire.
If we don't hear any traffic
received or sent in that amount
of time, we're going to tear
down your operation to tell you
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of time, we're going to tear
down your operation to tell you
that the network timed out.
We're also going to expose
an end to end timeout,
and we expose this as
the timeout interval
for resource property
on a CKOperation.
This defaults to seven days and
it governs the amount of time
that we're willing to wait for
an entire network round trip
from your device to
the service server
and its completion
back to the device.
Now, I want to make note
that a CKOperation may issue
multiple network requests
as it's going about its job.
So, a CKOperation may take more
time than you expect so long
as you are making
progress in the wire.
Next, I want to talk about how
do we efficiently fetch a series
of record changes when
there are many Record Zones
up on the server.
As we'll learn when
we get into sharing,
your client may see more
Record Zones then you've seen
in the past.
So, our answer to this
used to be that you need
to fetch the entire
list of Record Zones
from a database using a
CKFetchRecordZonesOperation.
There's a couple
problems with this.
We don't want you to poll
and we don't want to have
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
We don't want you to poll
and we don't want to have
to fetch the entire list of
Record Zones down from server.
So, we're no longer going to
recommend this for this approach
and we're going to replace
it with two new concepts.
The first,
CKDatabaseSubscription.
This is a new subscription type
that will fire whenever
any change happens inside
of a database.
Even in a Record Zone that
you haven't learned about yet.
And we're going to couple
that with a
CKFetchDatabaseChanges
operation.
This is an operation that allows
you to ask the server for a list
of Record Zones that
have pending changes
since some point in
time in the past.
Okay, so now you have a list
of Record Zones that you want
to go fetch changes for.
How are you going to do that?
Well, the old way was
that you would issue a
CKFetchRecordChanges operation.
You'd pass in a single Record
Zone and get the changes
for that single Record Zone.
We don't want you to have
to enumerate, you know,
sequentially through all these
Record Zones so we've gone ahead
and deprecated this
operation outright.
And we've replaced it
with a brand-new operation
with a very similar
sounding name,
the CKFetchRecordZone
changes operation.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
the CKFetchRecordZone
changes operation.
This is essentially a batch
interface over the old operation
and it gives you the ability
to fetch record changes
across multiple Record Zones
in a single network round trip.
So, let's go ahead
and visualize this.
Here we have a database,
several Record Zones,
each Record Zone has
a series of records.
And the client, which
is up-to-date
with all of these changes.
Now, along come a
couple of new records.
Your client, by virtue of
having previously saved a
CKDatabaseSubscription, will
cause a push to be generated
on the server and
sent to the client.
Next, using a
CKFetchDatabaseChanges
operation, you can ask
the server for a list
of Record Zones that
have pending changes.
In this case, the
first and the third.
Now, armed with that
list of Record Zones,
you can issue a
CKFetchRecordZoneChanges
operation requesting all
those records and all
of the change Record Zones in
a single network round trip.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And lastly, I'd like to talk
about how do you
efficiently fetch changes
when there are many
records sitting
in a Record Zone
up on the server.
If you've used CloudKit
to do this in the past,
then you're familiar with the
moreComing flag, which was set
on a CKFetchRecordChanges
operation to inform you
that not only have we given you
some changes but there are more
up on the server that
you should go fetch
with a subsequent
CKFetchRecordChanges operation.
Now, there's a couple
problems with this approach.
The first is that we've
distributed the logic
of check the flag and
issue another operation
to all of our clients.
It's another potential
point of failure.
And secondly, while you're
determining that you need
to fetch and cue a new operation
and doing that in cueing.
CloudKit is sitting around idle.
We want to address both of those
so we took advantage of the fact
that we made a brand-new
operation,
CKFetchRecordChanges
operation, to change this model.
Instead of us telling you
when there are more
changes available,
you tell us what your
intention is via the new
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
you tell us what your
intention is via the new
fetchAllChanges property.
When this is set to true, then
CloudKit will fetch a batch
of changes from the server,
hand them to your client,
and then immediately
go back to the server
for the next batch of changes.
This allows us to keep
the pipeline full,
pulling network data over the
network while you're processing.
Now, we think that this is going
to be such a common behavior
that we've gone ahead and we've
made this the default behavior
for this new class.
So, new CKFetchRecordZoneChanges
operations
by default will fetch
the entirety of records
down from a particular
Record Zone.
As you might imagine, if you've
got a large Record Zone, say,
your user's iCloud Photo
Library up on the server,
this means that the subsequent
operation to fetch all records
in the Record Zone is going
to take a very long
time to complete.
We want to make sure that
you are resilient in the face
of operations that
fail part way through.
We don't want to have to go
ahead and re-download batches
that we've already
fetched from the server.
So, we've added a new
callback on this new class.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
RecordZoneChangeTokens
UpdatedBlock.
And after we hand you a batch of
changes, we're going to go ahead
and tell you about an
updated server change token.
And your code is going
to be responsible
for doing two different things.
First, you're going to go ahead
and commit all the
per record changes
that you've received
from the server.
And secondly, you're going to go
ahead and you're going to cache
that server change token.
If the operation fails at
some point in the future,
you can issue a brand-new
CKFetchRecordZoneChanges
operation, pass in this locally
cached server change token
and essentially pick back
up where you left off.
No need to re-download
the batches of changes
that you've already
downloaded from the server.
And so these are just 4 of the
API improvements that we hope
that you can take advantage of
as you write applications backed
by iCloud, backed by CloudKit.
And with that, I'd like to
go ahead and switch gears
and invite up Jacob Farkas to
walk us through the sharing UI.
[ Applause ]
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
>> Thanks Paul.
My name is Jacob Farkas
and I'm an engineer
on the CloudKit team.
And today I'm going
to talk to you
about how you can add CloudKit
sharing UI to your application
by only writing a
couple of lines of code.
We've introduced a new class
in CloudKit called CKShare.
It's a subclass of CKRecord
and it's responsible
for storing two important
pieces of information.
One, what is shared, and two,
what that record is
being shared with.
So, let's look at
an example of this.
We've got our private database
here and we have a note
in the private database
that we'd like to share.
To do that, we're going
to create the CKShare
and initialize it using
that record as the record.
You always need to create
a Share with a root record
so there's always
something in the Share.
Next, we're going to save
that Share and the root record
to the server at the same time.
You want to do that because
there's a new property
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
You want to do that because
there's a new property
on CKRecord that's a reference
to the Share that
we're creating.
By saving the root record and
the Share at the same time,
that reference will be set to
the share you just created.
So, now we've defined what
we want to share but we need
to define who we want
to share that with.
To do that, we've created a
new lookup service in CloudKit.
This lookup service takes a
email address and it turns it
into a CKShare participant.
You can set the Share
participant on the Share,
save that Share to
the server and now
that person's iCloud account
has access to the Share.
We also support looking
up users via phone numbers
or CloudKit user record IDs.
Now, we want to let users
have control over what appears
in their shared database so
we don't want to these records
to just instantly appear.
The user should have control so
they should be able to accept
that Share and join it.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that Share and join it.
But that means we need a way
of telling that other user
that we've made a share
for them and invited them
and that they need to join it.
And we do that via URLs.
Every share has a URL which
uniquely identifies it.
If the user taps on this URL in
iOS or clicks on it in macOS,
we're going to show
the accept UI.
We're going to ask them if
they want to join the Share.
And if they do, they'll
be taken to the App
and shown the items
in that Share.
The great thing about a
URL is that if this user is
on an older platform
or on a platform
that doesn't support sharing,
this will take them
to iCloud.com.
And we can show them
information about the Share
and tell them how they can
accept it and join the Share.
So, let's put this URL into
an email and send it off
to the other participant
we invited.
They're going to receive the
email, click on it and now
in their Shared Database they
see the Share and the Note
that we created and
shared with them.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that we created and
shared with them.
The great thing here is that the
Share Database is actually just
a view into the owner's
private database.
So, if this other user has
the right access to the Share,
if they update that
Note, we're going to see
that same change happen
in our private database.
So, let's take a look at what
this looks like in the UI.
All right, we've got Notes
here and we've added sharing
to Notes in macOS X Sierra.
By using the same
CloudKit sharing APIs
that we're making available
to all of you today.
So, you'll see that there's
a new Share Add Person button
up here.
And if we tap on that,
we get a new sheet
that lets us choose how
we want to share that URL.
When we hit Share, the system
UI is calling into Notes
and telling Notes that it
needs to save that Share
and the root record
to the server.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and the root record
to the server.
Once it's done that, the system
UI shows a Mail Compose window.
We can invite the other user.
We hit Send.
And the system UI is actually
saving that Share to the server,
looking up the participants
and sending the email
off to the other user.
So, if we switch over to our
iPad here with the other user,
we see the email we just sent.
We can tap on that URL.
And we'll be asked if we
want to join the Share.
When we do that, we're
launched right into Notes.
The Share shows up, the Note
downloads and now we're sharing
that Note with the other user.
If I make changes on the
Note from the originator,
let's say I check off avocados
on the list and I add limes
as something else to pick up.
We'll see those happen in the
note that's being shared to us.
So, let's look at
the code behind that.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So, let's look at
the code behind that.
You're probably all familiar
with the CloudKit framework
already which is where CKRecord
and the new CKShare object live.
If you want to use this new
system sharing UI, you're going
to find that on macOS in
AppKit and in iOS on UIKit.
We'll start by looking
at the iOS sharing API.
Before we create a Share,
before we bring up the UI,
we need to create
a Share of course.
So, we'll create a Share
here with our record.
We will set a couple properties
to let the UI show that Share,
title and a thumbnail.
And then we move on
to creating a cloud,
a UI cloud-sharing controller.
We initialize that with
the Share we just made
and we pass it a
preparation handler.
This preparation handler
is going to be called
when it's time to
save that share
in the record to the server.
So, our handler here will create
a CKModifyRecords operation.
Save the record and Share to
the server and when it's done,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Save the record and Share to
the server and when it's done,
it will call the
completion handler.
Next, we might want
to set some properties
on this UI cloud
sharing controller.
One of the properties we can set
it is the available permissions.
We can say whether we want that
Share to be publicly shared only
or maybe we only want
to give the participants
read/write permissions.
We also want to set the present,
presentation controller source
view so that the pop-up appears
in the same place as the button
that we tapped to add people.
We'll want to set
ourself as a delegate
so that we get callbacks about
what's happening in the UI.
And finally we call Present.
And when we do that, we're
going to get a pop-up
that looks something like this.
Now, if you've already saved
the Share of the server,
you can call UI cloud sharing
control with just the Share.
And it will present a
list of invited users
and let them manage
the users on the Share
and stop sharing if they'd like.
Everything is taken care of
for you by the system UI.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Everything is taken care of
for you by the system UI.
The macOS sharing API is really
similar so we're just going
to go very quickly and
highlight the differences here.
First off, you create
an NSItem provider
and you register your
CloudKit Share with that.
This handler looks the
same as what we saw before.
You save the Share in
the record to the server
and when you're done, you
call the completion handler.
Next, you're going to
create an NSSharingService.
That sharing service is
going to have a delegate
that you set yourself
and you call perform
with the NSItem provider
that you created earlier.
Finally, NSSharingService
is callback based.
So, if you want to set options
on what the share can do,
you'll do that with callback
like options for Share.
On macOS, the Share create
UI will look like this.
And if you want to
modify the participants
on a Share, it'll
look like this.
Next, if a user accepts a
Share for your Application,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Next, if a user accepts a
Share for your Application,
your Application is
going to get launched
and it'll receive this callback
Application user Accepted
CloudKit Share.
That callback will contain
Share metadata that'll tell you
about the Share in
the root record
that the user just accepted.
It looks really similar
on iOS with the exception
of using UIApplication
instead of NSApplication.
And finally, you need
to tell the system
that your Application
supports CloudKit sharing.
And you do this via the
CKSharingSupported key
in your info P list.
We're also happy to announce
that we've added
full sharing support
to our CloudKit JavaScript
library so if you're on the web,
you can create Shares, accept
them and we've given you some UI
that you can use to
manage the Share.
You can try this all out right
now in the CloudKit catalog.
So, I'm going to hand things off
now to my colleague, Vanessa,
who is going to tell
you a little bit more
about sharing in depth.
[ Applause ]
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Applause ]
>> Thank you, Jacob.
Hi. And good afternoon.
My name is Vanessa Hong
and I'm an engineer
on the CloudKit server team.
So, today we will deep dive
into sharing by looking
at some common use cases.
We'll start with the
data that's being shared
and then we'll go
step-by-step all the way
down into the internals
of the CKShare object.
Then I'll talk about how you
can call our sharing APIs
if you want to create
your own custom UI.
And then finally we'll close
it out with some special notes.
So, let's get started.
Jacob showed how to
Share a single record.
But the item the owner wants
to share may not
be a single record.
It may consist of many records.
Possibly already
linked via CKReferences.
And your application
may want a participant
to see only a subset
of these records.
This is why we introduced
a new field
on the CKRecord called
the Parent Reference.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
on the CKRecord called
the Parent Reference.
Set the Parent Reference on
any records that you wish
to be included in
the shared hierarchy.
And you can set this up
even before the user decides
to share.
When the user does share,
you will create the CKShare
only with the root record.
Then, all of the all
the descendent records
that are linked to the root
record via the Parent Reference
are automatically included
in the shared hierarchy.
So, let's see what this looks
like in the shared database.
A shared database is only a view
into the owner's
private database.
So, it doesn't contain
any physical records.
When a participant
accepts a Share,
they only see what
is shared to them.
So, they see the
shared hierarchy.
This means there's no two
copies of these records.
There's only one copy
and that copy lives
in the owner's private DB.
So, this means the owner and all
the participants are interacting
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So, this means the owner and all
the participants are interacting
with the same set of records.
This kind of contention may
end up causing conflicts.
To learn how to deal with
conflicts, I'd like to refer you
to a past WWDC talk called
Advanced CloudKit from 2014.
Now, a read/write
participant can modify,
remove and add records.
But we don't want
them to be able
to add just anything they
want into somebody else's DB.
For instance, they cannot
add a random root record.
They also cannot add a record
without a Parent Reference,
even if it's somehow linked
to the shared hierarchy.
So, the correct way to add a new
record via the shared database
is to set a Parent Reference and
link it to the shared hierarchy.
So, even though you're
adding a new record
for the participant via
the shared database,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that new record lives in
the owner's private DB.
So, what this means is
all records that are added
by the participant are counted
against the owner's quota.
So, the producement's
quota is not affected
and your developer
quota is not affected.
The owner's private
database is the only place
that we store these records
so we can count them only
against the owner's quota.
And that's how you
share multiple records.
Let's take a closer look
at the shared database.
So, here we have two Shares
from two different owners
but the Shares have
the same name,
so how do you tell
the difference?
Well, we glossed over a very
important detail which is
that all records in
CloudKit live in Zones.
And a Zone is identified
by that CKRecord Zone ID.
The Zone name is the name of
the custom Zone that you created
in the owner's private DB.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
in the owner's private DB.
An owner name is the
owner's user record name.
So, in our example, the two
Zones have the same name
but different owners.
So, let's say the first
owner shares something else
but in a different Zone.
So when you call the
FetchDatabaseChanges API,
you will see this
new zone appear.
And then when you call
FetchRecordZoneChanges,
you'll see the new
record and the Share.
Now, let's say the second
owner shares something else
but in the existing Zone.
Well, this Zone already exists
so we won't create a new one.
We'll just reuse it.
When you call the
FetchChanges APIs, you will see
that this Zone has changed and
that there are new records.
And that is our shared database.
So, let's take this
one level down and look
at the CKShare object.
So, before the owner
can create a Share,
they must do something to Share.
So, the records describe
what to Share.
And the CKShare describes how
those records should be shared.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And the CKShare describes how
those records should be shared.
So, we're going to be
looking at the how.
So, as Jacob mentioned,
every CKShare is a CKRecord
but it has some additional
properties.
And we've been looking at
how these properties apply
to the lifecycle of a Share.
So, we're going to
start from the beginning
and the owner will
create a Share.
And the owner has to decide
what is the public permission
for the Share.
So, in this case the owner
says it should be none,
because he wants to
invite participants.
And let's say he
invites two participants.
Their status is automatically
invited.
And then the owner
decides what permission
to give to each participant.
Then, the owner saves the Share
and then he gets a
URL for the Share.
So, there are two
things happening here.
One is that the Share
has a state.
And the State says only these
two participants can accept
the Share.
The owner is the one with the
URL and it is his responsibility
to tell people about this URL.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to tell people about this URL.
So, even if he tells 100
people about this URL,
only these two participants
can accept the Share.
So, when a participant
accepts the Share,
they accept via the URL.
And after that accept,
their acceptance
status becomes accepted.
And then the permission
in the Share is exactly
what the owner gave them.
So, now let's say
the owner wants
to create a more open share.
So, let's start over.
The owner sets up a
Share and then he decides
that the public permission
should be readOnly
or read/write.
He doesn't add any participants.
He just saves the Share.
And then he gets a
URL for the share.
So, there's still
two things happening.
One is that the Share
has a state
and it says anyone can join.
And the owner has a URL.
And it's his responsibility
to tell people about it.
So, if he tells 100 people,
then all 100 people can join.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So, if he tells 100 people,
then all 100 people can join.
So, when they join, they
would have to join via the URL
and then that participant
appears
in the Share and accepted state.
Their permission is inherited
from the Share's
public permission field.
And that's how you set up the
Share and accept the Share.
So, the next phase of
the Share's lifecycle is
when a participant leaves.
And a participant
can leave a Share
by deleting the CKShare
object from their shared DB.
This will also remove the shared
records from their shared DB.
So, to be clear, the
CKShare still exists.
It exists in the
owner's private DB.
It's just that this
participant no longer is
in the Share in accepted state.
And the owner has full
power over his Share
so he can remove
anybody he wants.
Let's say he wants
to remove everybody.
He would do that by
deleting the CKShared object
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
He would do that by
deleting the CKShared object
from his private database.
This will also remove
the pointer
from the root record
to the Share.
And now the owner is back in the
initial state of being unshared.
So, let's move on and talk about
the CKShareParticipant object.
So, if you've seen this object
before in the lifecycle,
you saw the acceptance
status and the permission.
But now let's look at
the user identity field.
This has a look up info.
And the look up info is how
this participant was invited
to the share.
So, it will have their email,
phone or user record ID.
And the name components
are the first and last name
and this is populated with
when the participant accepts
the Share.
Every CKShareParticipant is
mapped to an iCloud account.
So, let's say the owner invites
4 participants and we were able
to find iCloud accounts for
the first two but we couldn't
for participant 3 and 4.
This is perfectly okay.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
This is perfectly okay.
CloudKit will create a
temporary placeholder
for participant 3 and 4.
And the only people who can
accept as participants 3
and 4 are the ones who can prove
that they owned the email
address or phone number
that the owner invited
them with.
This is called the
verification flow.
This will link the email
or phone to their account
so that they never have to go
through the verification
flow again.
And that's all the objects
that we have in sharing.
So, now let's move on and
talk about sharing APIs.
So, if you want to
create your own custom UI,
you can call our APIs.
And there are two
things that you can do.
So, on the behalf of the owner,
you can help them
set up the Share.
On behalf of the participant,
you can help them
accept the Share.
On watchOS and tvOS, there's
no built-in system UI.
So, you can ask your user to go
to a different platform to set
up a Share and accept the Share.
And then the Share data is
available across all platforms.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And then the Share data is
available across all platforms.
Alternatively, you can
just call our sharing APIs.
And this is how you do it.
On behalf of the owner, you
can help them add participants.
You would have to look up by
email, phone or user record ID
and then translate that to
a CKShareParticipant object.
Once you have the
CKShareParticipant object,
add those to the share.
And then call CKModifyRecords
operation to save the Share.
Now your application
has a URL for the share.
And it is up to you, the
application or the owner,
to tell people about the URL.
When a participant
accepts a Share,
we always start with the URL.
You first have to convert the
URL to CKShareMetadata object
and then pass that
metadata object
to the CKAcceptShares operation.
Now, the participant will show
up in a Share in accepted state.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, there are some
limitations to the accepted API.
For privacy reasons,
we cannot return
to you their name components.
And the verification
flow is not available.
So, if you get this error
or if it has iCloud
account Boolean is false,
then you can ask your user to
open up the URL themselves.
This will trigger the system
or the web to take them
through the verification flow.
And that's our sharing APIs.
So, now let's talk
about your users.
A user of your application can
invite anyone they want via any
email or any phone number.
Now, what this means is
the potential audience
for your application
is much larger
than your current user base.
So, these MIT's may not
have installed the latest
operating system.
They might not even
own an Apple product.
So, when they click on the
URL, we take them to the web.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So, when they click on the
URL, we take them to the web.
And in the example for Notes,
this is what they'll see.
They will be asked
to join a Share,
after which they'll
see the shared Note.
And they can interact
with this Note just
like they would on a device.
But this is the Notes
Web Application
that lives on iCloud.com.
What about your Application?
Well, by default, your users
will see something like this.
It has your App icon
and it asks your user
to go on the latest device.
Which is not the
ideal user experience.
So, I do have some
good news for you.
You can go to your
CloudKit Dashboard
and configure a fallback URL.
So, when an invitee clicks on
a URL that is shared to them,
we redirect them to
your fallback URL.
We'll append the token
that is from the unique URL
for the Share so that you
can immediately take them
to accept the Share and then
show them the shared data.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to accept the Share and then
show them the shared data.
Now, I hope you're excited
to get started on sharing.
There is just one last
thing that you need to know.
A CKShare is of this
new record type
and this record type
behaves just
like any other record
type in CloudKit.
You can create custom
fields on it.
You can run queries.
You also have the first created
in the development environment.
And the easiest way to create
it is just log in as a user
in your dev environment
and share something
from your private database.
This will trigger the
creation of the record type.
And then go to your
CloudKit Dashboard
and deploy your scheme
into production.
If you don't do this, then users
in the production
environment may get errors
when they create the Share
because the record
type doesn't exist yet.
And that wraps it up.
So, you learned today that
CloudKit is available on all
of our platforms including
watchOS and is available
on the web via CloudKit JS.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
on the web via CloudKit JS.
Telemetry is available on
our CloudKit Dashboard.
It's a great way to visualize
your application's behavior
including error trends.
There are many API improvements
including Long-Lived Operations,
[inaudible] and the
new fetch changes APIs.
And now you know all about
our new feature, sharing.
You've seen this system
UI and you know how
to create your own custom UI
by calling our sharing APIs.
And you've seen all the
objects that we used
in sharing including
the sharers lifecycle.
And I bet you will go back and
configure those fallback URLs.
So, I want to thank you for
sharing this experience with us.
I want to draw your attention
to CloudKit Best Practices.
It's tomorrow at 9 AM.
It's a great session on how to
use CloudKit more effectively.
Thank you and enjoy the rest
of your WWDC conference.
[ Applause ]