CloudKit makes it easy to store and retrieve any kind of data from iCloud. Dive into the API with the CloudKit framework team as they explore some of its lesser-known features, explore best practices around subscriptions and queries, and reveal its hidden gems.
NIHAR SHARMA: Good afternoon and welcome
to the CloudKit tips and tricks session.
My name is Nihar Sharma and I'm an engineer
on the CloudKit team.
I know some of you may be completely new to our platform
and encountering the CloudKit framework
for the first time while others may already have an app
on the Store.
This session we will have something for everyone.
Let's jump right in.
What is CloudKit?
Last year we introduced CloudKit as a whole new way for you
to be able to talk to Apple's iCloud database servers.
With that we gave you a set of built-in technologies
like large file storage.
We gave you a privacy conscious identifier to be able
to manage the users who could now be anyone
with an iCloud account.
First and foremost, we made this public facing developer API
because we wanted you to be able to leverage the power
of this platform and build great apps for your users.
Last but not the least, Apple's heavily invested
in this technology.
Last year alone when we first shipped,
we shipped with a couple
of major clients including iCloud drive
and iCloud photo library, and this year we added a host
of new clients like the Notes app, the news app
and WWDC app a lot of you have been using throughout the week
and have in your hands right now.
If all of this sounds unfamiliar to you, I invite you to go back
and take a look at the intro to CloudKit
and Advanced CloudKit sessions from last year's conference.
They are a great resource for an introduction to the new API,
and I highly recommend you check them out.
First things first.
A lot of you have been playing
with the amazing new features of Swift 2.
I'm pleased to announce with iOS 9, the experience
of using CloudKit from Swift is much better.
Let me give you a couple of examples
of what I'm talking about.
Up until now, you had to use the old set object for key,
object for key syntax when setting and getting values
in the CK record, which are the workhorse of the CloudKit API,
but with iOS 9, you can use the much more familiar
and modern dictionary subscripting syntax
when working with CK records.
In addition to that, we have made your CloudKit code
from Swift as well as Objective-C a lot more type safe
by adopting the libidity qualifiers
and Lightweight generics.
Previously you could have set an array of objects of any type
on the records and safe product of CKRecords operation.
Now with the latest tools in iOS 9, the compiler can warn you
when you do that so you can catch the errors early
and write more robust code.
So with that, let me give you a brief recap
on the storage architecture of CloudKit.
The top level silo in CloudKit is called a CloudKit container.
It is subdivided into two databases.
The public database, which is a large soup of all
of your apps data shared among all of your users,
and the private database which is tied
to a user's iCloud account.
This daatabase will contain data
for a particular iCloud account shared across all
of that user's devices.
Within each database we have a further layer of isolation
for the records you store in them,
and we call these record zones.
They are a way for CloudKit to offer additional capabilities
for the records you store in them where we can.
If the public database has a single zone called the default
zone where all the records live,
and the private database also has one default zone.
Along with that, we give you the capability
to create multiple custom zones
where you have these additional capabilities for your records.
So with that, let's talk
about what we will cover in our session today.
You might remember the schema from last year.
We talked about an example schema for an app
that shows parties with clouds.
It had a simple schema where we had a party record type
and clown record type and stored them in the public database.
I thought this year let's run with this example
and develop a couple of features for this app,
example app we'll call clown central
because it's all about clowns.
We will use the example to walk through a set of tips and tricks
that you can use when working with CloudKit.
Our app will have a simplistic UI where we show the list
of parties and a couple of features that we will walk
through together in this session.
Now, truth as example, there are four major areas I want
to cover today.
Number one is error handling.
Last year we told you a difference
between a CloudKit app that handles errors and one
that does not is not the difference
between a great app and a good one.
It's a difference between a functional app
and a completely broken one.
We meant it.
I would like to walk you through a set
of special error codes you may encounter when using the API
and give you some general guidelines
on how to handle them.
With that, we'll start talking about a couple of tips
that you can keep in mind when maintaining a local cache
when working with CloudKit.
That will lead us into talking about how to get set
up with subscriptions to keep our cache up to date,
and finally I would like to talk about a set
of general purpose performance tips that you should keep
in mind and adopt in your apps today.
So we've got a ton of great stuff to cover.
Let's jump in and talk about error handling.
The first thing that I would
like to do is talk about accounts.
CloudKit does not require you
to have an iCloud account to be used.
We allow anonymous read only access to the public database.
Let's say for demonstration purposes here
that the clown central app will require an iCloud account.
We talk about a couple of features
that use the private database which, by definition,
require an authenticated account.
And by default, write access
to the database requires an account as well.
As a reminder, the way you check the account status
for the current user is by using the account status
with completion handler API, available on CK container.
Any errors that you encounter when working with CloudKit due
to authentication will fail
with a special error code called CKError not authenticated.
The general guideline we give to handle this error is
to recheck the account status.
Let's say we have a missing iCloud account.
When you check the account status,
you receive CKAccount status no account.
Previously you had no way of knowing when requests
that failed due to a missing account would start
For that very purpose with iOS 9 and OS X El Capitan,
we added CKAccount change notification.
We will send you this notification whenever there is a
change to the user's account, for example on log ins, log outs
or if the iCloud drive capability switch is turned
on or off.
With that I would like to touch on a couple of best practices
when handling a missing account in your apps.
It might be tempting when encountering this situation
to throw up an alert for the user telling them they don't
have a logged in iCloud account and can't proceed.
This is not helpful to the user
because they might dismiss the alert and retry an operation
that led them to see the alert in the first place.
What we recommend instead is
that you gracefully degrade your UI in a way
that simply disables the features of your app
that require an account, and for this purpose you can now use
CKAccount change notification to re-enable that UI,
when you receive it, re-check the account status,
and see that one account is now available.
A missing account is not one of the only conditions
under which your operations might fail temporarily,
but may start succeeding at some point in the future.
For example, under poor network conditions
where you might encounter this error, CKError network failure,
or if the CloudKit servers are busy.
Or you might see one of these errors:
CKError service unavailable or CKError zone busy.
When encountering this error, we want you to retry the operation
at a later date, but you might be wondering
when do I retry those operations?
Well, you don't have to guess at that value.
In these errors, user info dictionaries we return
to you a special value under the key "CKError retry after key."
This value is a value of time in seconds that you need
to wait before retrying that operation.
Now, let's take a similar example
where let's say our app initially had a bug
which might have caused it to send a lot of updates
to the server in a very short amount of time.
Let's say if this app made it to the wild in that way and a lot
of users started hitting that bug,
it would overwhelm the iCloud servers.
The way we avoid this is
by using a special error code called CKError request
This is CloudKit's way of mitigating application bugs
from overwhelming the iCloud servers.
Any requests that hit the rate limited error will not be sent
up to the server until a period of time has elapsed.
Once again what is that period of time?
It is given to you by the CKError retry after key.
So when you encounter this error, look for this key
in the errors user info dictionary.
Wait for a period of time, and retry your request.
Now I would like to start talking about a different class
of errors that you might encounter
because of the way your schema is designed, specifically
if your schema allows multiple users to update the same record
in your Cloud database.
So let's say we want to add a feature to our app
where we allow attendees to add themselves to a party.
But unfortunately, when designing the schema
for this feature, we did not watch last year's advanced
So this is a schema we came up with.
On the party record itself, we decided to store an array
of references to attendee records
that want to join that party.
Now, you can see that every single time we wish
to add an attendee to a particular party, we are going
to end up modifying the same party record.
Let's take a look at an example of what happens
when two different users try to add themselves to a party.
Since the WWDC bash is starting up soon,
let's say we saved this record to CloudKit.
And now before we get into what happens
when two users download this record, I would like to talk
about what are record change tags.
You can think of them as simply a string that the server uses
to identify a particular version of a record.
This version of the record as it exists
on the server is recognized by the change tag A.
We expose this to you as a read only property on CKRecords,
but it will only be populated on records that have been saved.
Let's say two users, John and Alice, come along
and download this particular version of the record.
You can see they receive the same change tags, A.
Now, John adds himself as an attendee to the party first,
goes ahead and tries to save his record to the server.
Now, with the records saved, we will send the change tag
that John had, which is A, up to the server.
And the server sees that the change tags match
and accepts John's modification.
Now, since the version of the server record has changed,
the server will generate a new change tag in this case, B,
and send that back to John in the record save response.
Now let's say Alice comes along and decides to attend the party.
She tries the same operation, adds herself to the array
and tries to save her version of the record.
This time you can see that she will be sending
up the old change tag A, and the server will complain
that she is trying to alter a version of the server record
that no longer exists.
She encountered a conflict.
On her device, the way CloudKit tells her about this conflict is
by a special error code called CK error: server record changed.
There's no magic happening behind the scenes,
and we don't make assumptions
about how you wish to resolve conflicts.
You are the best person to do that.
So we will try to provide you with as much useful information
as we can for you to resolve those conflicts yourself.
And the first and most important piece of information
that we give you is the version of the record as it was
in the server when an update was rejected.
Where do you find that?
You find that once again in the errors user's info dictionary
under the key CKRecord changed error, server record key.
In this case, when we pull it
out of the errors user's info dictionary,
we would find the record as it was in the server
with John attending the party and the new change tag B.
Now, in addition to the server record,
we give you back a few more pieces of information.
These include the ancestor record key which is the record
as Alice had before she made any modifications to it.
And the client record key which will contain the record
that Alice tried to save to the server.
Now, what I want to emphasize here is
that the most important thing to do, and what you will be doing
in most cases when resolving a conflict,
is trying to save the modifications that you were
in the first place before you encountered the error
but instead on to the server record returned
to you by the error.
So in this case, you take the server record.
We'll make the same modification to it that we were trying
to save, which in our case is simply add Alice as an attendee
to the party, include her along with John, and save this version
of the record to the server.
You can see that we have the server's new change tag B.
When we save the record those change tags will match,
and the server will accept the save.
Now, a point to note here is
that we could have avoided this entire class of errors
if we had used a better schema for this feature.
I'll talk about what that schema is in a short while.
But you can see that trying
to modify the same record every single time a different user
makes that modification is not the best idea.
So talk through this new schema,
let's look at CloudKit operations.
We want to add a feature to our app that allows users
to store photos for parties.
We need a similar one-to-many relationship between parties
and photos this time, so photos would be their own record type,
but we don't want to store them on the party record this time.
How do we do this?
We can save the photo records with a back reference
to the party that they belong to instead.
You can see now when we save photo records, we don't have
to modify the party record that they belong to.
So let's talk about how we are saving these records.
Let's say right now in our app we are using the convenience
API, save record with completion handler
to save one photo record at a time.
But users could potentially store multiple photos at once.
In that case, we are currently using the convenience API
in the tight loop to save multiple records.
Let's take a look at what is happening behind the scenes
when we do that.
The app calls the convenience API a bunch of times to be able
to save multiple photos.
Each one much those in the system gets wrapped
into a CK operation with a set of default values, and each one
of those operations turns into at least one network request
when we try to save that record up to the server.
We are not going to overwhelm the server with all
of those requests at once, so we have also created a bottleneck
in the system, and the system sends up a few requests
at a time in order to save those records.
Now, in addition to this bottleneck,
there is one more thing that you should consider.
Every single one of those requests to save one record
at a time, for example in this case,
counts against your network request quota
as CloudKit app developers.
This is clearly a bad idea.
We want to be able to batch those record updates
into one network request, or at least the minimum number
of network requests possible.
How do we do that?
Well, we do that by using the CK operation counterpart
to our convenience API.
Almost every convenience API that works on one item
at a time has a CKOperation counterpart
that batches record updates together.
In this case we want to use CKModify Records operation
to be able to save multiple records at once
by providing them as an array to the record save property.
Look at what happens when we adopt this operation.
Now we can bunch all of the records that we want
to save into one operation.
It queues in the system.
The system is able to use the minimum number
of requests it needs to be able to save those records
to the server, and we eliminated the bottleneck.
At the same time we've helped you optimize the use
of your request quota.
This is an important point I would like all of you to think
about in your apps when using the convenience API.
If you are ever using it for the same kind of request
in multiple places or some kind of loop, think instead
of adopting the CKOperation API
that lets you batch those updates.
It will save you your request quota,
and at the same time be more efficient for the system.
Now that we are working with batches,
there is an additional consideration
that we need to think about.
The server imposes certain limits on the sizes
of the batches that can be sent up at once.
These limits include the number of items in each request,
as well as the total size of the request.
The total size of the request is simply the sum
of the key value data that you set in the records
that that belong to that request.
An impportant thing to keep in mind here is that the size
of the data the you are trying to store as part
of bulk stoarage via the CKAsset API does not count towards this
key value data.
But if your request were to trip any one of these limits,
you would receive a special error code called CKError
The general guideline we give developers
to handle this error is simply divide the number of items
in your batch by half and issue two operations instead of one.
And recursively do that if those operations encounter the same
Now, what if only some items in your batch were to fail?
Since the batch consists of a lot of items
but returns only one error to you, we still want to tell you
about every single one of those errors.
We do that by using a special error code called CKError
This is a top level error code that you don't really want
to handle directly, but once again
under the errors user's info dictionary,
if you look under CKPartial errors by item ID key,
we will give you a dictionary of item IDs
to the corresponding errors from your batch.
For example, in this case we have had one item ID that failed
with CKRecord invalid arguments, and there may
or may not be errors for any other items in your batch.
You want to open this up, look inside the dictionary and handle
that error individually.
This situation changes slightly
when considering atomic updates in custom zones.
Custom zones, as a reminder, have the capability
for your CKModify records operation
to issue atomic updates,
in which case the server will either accept the entire batch
as one or fail the entire batch.
Now, if one item in our batch, as in this case,
would have failed with CKError invalid arguments,
the rest of the item IDs would also contain an errror
with a special error code, CKRecords batch request failed.
When working with atomic updates,
make sure to look inside the dictionaries
and handle all the errors that are not
in CKError batch request fails.
That's storing all of our photo records up to the Cloud
in an optimized manner.
Let's talk about the other half, downloading them.
The way we do that is by using CloudKit queries.
Downloading photo records
for a particular party has now become really easy
with the new schema that we adopted
where photo records reference the party they belong to.
We do that by simply constructing a CK query
that tries to match that reference
to a known party record ID.
Now, when we issue our query to download photos for a party,
some parties might have a lot of photos.
Do we really need to download all of them?
Let's take a look at how we can issue an optimized download
for the photos for a particular party
by using CKQuery operation.
The first question to really answer is:
We have no idea how many photos belong to a particular party.
So how many should we download?
It doesn't make sense to download all of them.
What makes sense is for our UI
to drive the answer to that question.
Now, if you take a look at our example UI here,
you can see that when we pull up a particular party,
all we see are 20 photos.
So it would make a lot of sense
if our query only returned 20 photos to us
when we first issued it.
We can do just that by using the results limit property
on CKQuery operation.
This property helps a lot for you to be able to manage items
in a particular batch size
when you have no idea how many items might be returned
to you in total.
So for that reason it is also available
on CKFetch record changes operation
where you maybe returned a lot of changes,
and you have no idea how many from a custom zone
and on CKFetch notification changes operation
for a similar reason.
So now we are downloading just 20 records.
That's an improvement.
But can we do better?
Well, let's take a look at what we are downloading.
Once again we let our UI answer this question for us.
Whenever we are viewing a particular party,
all we are seeing is tiny thumbnails, cropped
and down scaled photos for a particular party.
But what we've stored on our photo record
that is being downloaded completely by default
for us is probably a high resolution version of that photo
that we've taken with the amazing cameras
on our iOS devices.
Well, wouldn't it be great if we could somehow add
that information right on to our photo record
so that we have something that we can pull down partially,
but how do we pull down partial records?
Well, we do that by using the desired keys property
on CKQuery operation.
In this case, the desired keys property will take an array
of keys that you wish to fetch on all the records
that match your query.
So if we set that just to be our photo thumbnail,
you can see that we have drastically reduced the amount
of data that we are loading when our query returns.
This is also available on CKFetch records operation
where you may know the record IDs in advance
of the records you are fetching, but either your UI
or some other reason you only want partial records
to be downloaded.
As well as on CKFetch record changes operation
which once again by default downloads the full record
for any records that may have changed.
So now that we're displaying only 20 photos, it makes sense
for us to have a certain ordering on the photos
that we are first displaying to the user.
Let's say we want to show the photos in the order
that they have been, in the order
that they were most recently saved into iCloud.
We do that by setting the sort discriptor right on the CKQuery
that we initialized our CKQuery operation with.
You can see here we are creating a sort descriptor
on the creation date key which is a system field
on all CKRecords that have been saved to the server.
And set that to descending.
One thing to keep in mind here, since this is a system field,
you need to ensure that it's sortable on the server.
You do that configuration via the iCloud dashboard.
Make sure to have
that configuration set before those records are saved.
Otherwise, the previous records saved previously are not going
to have that index on them.
So now that we are fetching just a small slice
of our entire results set, you may be wondering,
how do we show the user the rest?
Say the user starts scrolling down, and we want to look
at the next batch of photos.
How do we implement pagination in this case?
Well, we do that by looking at what we get back
in our query completion block.
When a query completes, in addition to all the results
that were returned to us in the progress call backs,
we get back a CKQuery cursor.
This is an opaque marker for you to use that shows you your place
in the entire results set.
So you should store the query cursor returned to you
from the first query operation, and when you wish
to fetch the next batch of results,
initialize another CKQuery operation using the cursor
initializer and pass it,
the cursor that you stored previously.
Now, since we are optimizing our CKQuery operation
in this manner, make sure to set the same desired keys
and results limit on the new query operation once again.
That will give you just the optimized next batch of photos.
That was about downloading records.
Now I would like to switch gears and talk about some tips
that you can keep in mind
when maintaining a local cache working with CloudKit.
Let's start talking about a new feature.
Let's say we want to add the ability for users
to store small personal Notes for parties.
Now, since these Notes are going to be personal,
we want to store them in the user's private database.
We don't want to fetch these Notes every single time a user
wants to view them or modify them.
We want to make sure that we have some kind
of offline access for these Notes.
And you can see that in this particular scenario what we
actually need is a small amount of data but on all
of a particular user's devices.
So it makes a lot of sense for us to maintain a local cache
when working with CloudKit in this scenario.
Let's first talk about how we can start downloading things
from a private database.
Now, if you recall, we have the ability to store custom zones
in the private database that gave us additional capabilities.
We go ahead and do just that.
Create a new zone in the private database called the notes zone.
And now we have two main ways
in which we can start fetching data from this zone.
Once again, we can either use a CKQuery operation
and optimize it just the way we saw,
or we can use delta downloads via the CKRecords fetch
operation which lets us fetch only the records
in the zone that have changed.
If you recall, this operation is only available to work on zones
which have the fetch changes capability.
Currently, all custom zones
in a private database do have this capability.
Now, if you wish to learn more
about how exactly delta downloads work, I invite you
to go back and look
at the advanced CloudKit session from last year.
It's a great walk through of how the operation exactly works.
Let's say we are using it.
We've started fetching our changes.
We have our app objects that we are storing in some sort
of local database, whether it's core data
or any other database of your choice.
That's where we encode our app objects currently.
So here we have a party object.
We see we've added the notes key on it corresponding
for that particular user's Notes for that party.
We encode our app object to our local storage.
When working with a corresponding CKRecords,
we want to store those records up in the Cloud.
We might think about encoding the entire CKRecord
so that we have that cached along with our app object.
Let's take a look at what happens here.
You can see that CKRecord also has all
of the app objects key set on it.
Of course, when we encode it we are duplicating all
of the apps keys.
Once we encoded our app object and now
when we are encoding our CKRecord.
This is clearly not what we want.
Well, the orange fields that you saw
on the CKRecord belong just to the CKRecord.
They are the fields needed by the server
to recognize a particular version of the record.
We call them system fields.
So what you really want in this case is a way
to encode just the system fields of the record.
And you can do just that by using the encode system fields
with coder API on CKRecord.
Now this is all the code that you need to be able
to encode those system fields.
I highly recommend that you reference this if you ever,
if the situation arises and you ever need to look back.
Now let's take a look at what happens
when we start encoding just the system fields.
We are efficiently storing now what is important
about a CKRecord and the corresponding party object.
Now, let's walk through a scenario of what happens
when we try to modify a party object
for which we've stored the system fields in this manner.
For that we use the coder initializer for CKRecord.
So you can see that when we pull it out,
we will get back all the system fields that we had stored.
For brevity I've only shown the record ID and the change tag
which we've already seen.
Now on this bare CKRecord it is completely legitimate for you
to set just the keys that have changed on this record.
So let's say we want to change just the party
and make this record our WWDC bash record.
We set the new value for that key
and save the new record for the server.
It is important to note that you don't always have to set all
of the keys that belong to a record
when storing changes for that record.
So now that we are officially maintaining and storing
that local cache, let's talk about how do we fetch changes
from our custom zone in order to keep that cache up to date?
Well, once again we already have the answer to this
by using CKFetch record changes operation which gives us all
of the records that have changed in our zone.
The real question is, when do we use this operation?
Because using this operation alone does not tell us
when our zone has changed.
So for that we need
to use notifications via the CKSubscription API.
More specifically, since the changes
in the zone are not changes that you wish to alert a user about,
what we really want here are silent notifications.
So in the next section, I would like to talk to you about how
to get up and running with subscriptions especially
when you want to use silent subscriptions.
Let's start with brief recap.
What are subscriptions?
Subscriptions are per user persistent queries
that you saved to the server.
They are a way for you, for your app
to receive remote notifications per relevant changes.
There are two types of subscriptions, and they differ
in the way you define what a relevant change for you is.
Number one, there are query subscriptions
which allow you to store a predicate.
So when the predicate values to true,
that's your relevant change.
The second ones are zone subscriptions
where every modification
to a zone counts as a relevant change.
So this is clearly what we want in the case of trying
to get silent notifications whenever our zone changes.
But first, let's walk through the general setup that you need
when handling all kinds of CloudKit subscriptions.
What I would like to emphasize with this setup is
that you still need to go through the motions of setting
up remote notifications
as if they were not coming from CloudKit.
Let me show you what I mean by that.
Number one, you still need the APS capability
for the app ID turned on from the developer portal.
This should get automatically turned on for you when you turn
on the CloudKit capability.
Number two, you need to set the APS environment key in your app
into a P list for development while you're testing your app
and expecting remote notifications.
Third, you still need
to register via the UI application API.
At the very least, you need to call register
for remote notifications and also call user,
register user notification settings if you are planning
to show user notifications in your app.
Now, since we are interested in silent notifications
and we're dealing with the CloudKit server
that sends us notifications, how do we tell the server
that this should be a silent notification?
We do that through CKNotification info
corresponding to our CK subscription.
That is our entry point
into telling the CloudKit server just what kind
of a push payload should be sent and at what priority.
Let's talk about priorities.
So like I said, we configure our CKNotification info in a way
that tells the CloudKit server
that this is a silent notification and it needs
to come at a low priority.
The server will send you a high priority push if you have any
of these keys set on your CK notification info.
Whether it's the alert body, should badge or sound name.
These are what we call UI keys for your subscription.
If you send any one of them,
the server sends a high priority push that is meant
to be delivered immediately.
All other pushes are sent at medium priority and count
as silent notifications.
So let's walk through what is a silent notification specific
setup that you need.
Number one, you need to turn
on the remote notification background mode for your app.
You do this through the capabilities pane in Xcode.
You should remember to checkmark that.
Number two, you should make sure
that you implement the application
that you receive remote notification,
fetch completion handler notification
of the application delegate API.
The other version is not going to be called in the background.
Make sure when you are expecting silent notifications,
you have implemented this version.
And third, once again now we need to tell the CloudKit server
that this is going to be a silent push,
how do we configure our CKNotification info?
First and most importantly,
you should set the 'should send content available' property
It tells the CloudKit server
that in your push payload it should include the content
Secondly you should not set any of the UI keys
that we just talked about on that CKNotification info.
Setting any one of these properties along
with should send content available is not a supported
configuration and will result in an error on the server.
So now let's talk about silent push delivery.
We've configured everything, we are expecting pushes.
When do we get them?
Since these notifications are not meant to alert the user
in any way, they are sent at a time
that is opportune for the system.
The system considers a variety of factors when deciding
when they should get delivered.
And push delivery in general is best effort.
What I mean by that is that pushes could get coalesced
or even dropped depending on the conditions of a device.
For example, if a device was in airplane mode when a flurry
of pushes was expected, coming out of airplane mode,
the Apple push notification server will only send the device
the last push that was meant to be received on it.
Now, we have ways to mitigate this because we are dealing
with CloudKit notifications.
In particular, CloudKit server stores all of the notifications
that were meant to be delivered to your device
in what we call a notification collection.
So when you do receive a silent notification,
you should make sure to fetch changes
from this notification collection,
and you do that via the CKFetch notification changes operation.
So now we are getting silent notifications, we are checking
if there are any notifications that we've missed,
and we know that our zone has changed
which is the reason we got the notification in the first place.
This is where we use CKFetch record changes operation
to see what has changed in our zone.
But once again like we've talked about before,
we have no idea how many things changed in that zone.
So potentially this could be a long running operation.
If you need a little more time for that operation to complete,
I recommend that you look
into the background task API on UI application.
This will let you get that extra time in order
for your operation to complete.
Now, before we start talking about notifications,
in iOS 8 we introduced an entirely new category
of notifications called interactive notifications
which allow a user to interact with pushes from banner,
alert or from a notification center.
And we have had a lot of requests from you to be able
to configure interactive notifications with CloudKit.
I'm pleased to announce with iOS 9 you can do just
that with minimal amount of setup.
Once again if you just set the new category property
on CKNotification info, it corresponds
to the identifier you that registered
with UI mutable notification categories
when registering user notification settings.
That is all the setup you need to get up and running
with interactive notifications with CloudKit.
NIHAR SHARMA: Thank you.
And with that, I would like to start talking
about a general set of performance tips
that you should keep in mind and use in your apps today
when working with CloudKit.
CloudKit is a highly asynchronous API.
Most operations talk over the network, and it is very common
to run into situations where you have a set of dependent tasks
and you want to maintain some sort of ordering
in which they complete.
Now, when implementing task management for these,
there are a couple of goals, a couple of high level goals
that we would like you to keep in mind.
Number one, obviously
that whatever technique you employ allows you
to implement great error handling for every single one
of your CloudKit tasks.
Secondly, since these are asynchronous operations,
you should make sure to never end up in a situation
where you block the main thread
and degrade their UI performance.
And last but not the least, as developers you want to make sure
that your task management scheme is, lets you end
up with maintainable code that is easy to reason about,
debug and extend as you add new features to your app.
Let's take a look at a couple of ways where we do this
and some dos and don'ts.
The number one don't is nesting convenience API calls.
Let's take a simple example.
If we had to modify one of the attendee records
in the old schema that we saw, once again never use
that schema in the real world.
But if you had to modify the attendee record,
this is what you would have to do
when using convenience API calls.
You would first fetch record with ID and try
to fetch the party record
that you know the attendees are part of,
then pull out the record ID for the attendee
from the attendees array, and then make your modification
to the attendee's record, and then try to save that record.
This is trying to modify one record
with one set of dependencies.
You can see we have ended up with code
that is just a mangled piece of soup where you have no idea
where to handle which error and how best
to retry those operations.
In addition to that, there is an additional point
of concern here.
Let's say that we issue these operations due to some sort
of user action in our app.
Now, if a subsequent user action were
to render these tasks unnecessary,
once you've enqueued them, you have no way
to cancel these tasks.
So if they are potentially long running, you are stuck
with them running, you are stuck waiting for them.
We recommend that you never use this approach
when managing dependent tasks especially if you need
to make the same modification for a batch of records.
Now, another technique that we see is to simply get rid
of the asynchronous nature of the API perhaps
by introducing a semaphore and waiting on it.
This can get hairy in a couple of situations too.
You should almost never try to do this.
If you do, you should keep in mind that especially
if you wait forever for operations to complete,
it it is very easy for you to end
up with circular dependencies that end
up causing a deadlock in your app.
Or if you were to ever use this practice on a main thread,
this will block your UI right away on an operation
that is likely waiting on the network and result
in a terrible user experience.
So we don't really recommend it.
What we do recommend is for you to take a look
at the dependency management API that NSOperation offers.
This is what I mean by that API.
NSOperation lets you easily add and remove dependencies
between other NSOperations.
Let's take a look at how this works with CKOperations
that are a subclass of NSOperations.
If we have two dependent fetch records operations
and the second one should not begin before the first one is
completed, all you need to do is set up both of those operations
and add the first fetch as a dependency on the second
and enqueue both of those operations.
This will guarantee that the second fetch does not start
before the first fetch is finished.
You can see this offers you a logical way to think
about the errors for particular operations
and at the same time manage dependencies for them
in a convenient manner.
Now, when thinking about NSOperations
from a performance context,
there is an additional distinction that I would
like you guys to think about.
Not all NSOperations are created equal.
Some of them may have been created
because of an explicit user action
in your apps while others may represent background tasks
that are of lower priority.
To indicate this notion of relative importance
between NSOperations to the system,
in iOS 8 we introduced the quality
of service property on NSOperations.
This property lets you indicate the nature and importance
of work encapsulated by your NSOperation.
These are the various service levels
that this property can take, and I recommend that you check
out the documentation for a description of each one
of these values and their significance.
But what is important to keep in mind here is that each
of these service values directly affects the priority
with which the NSOperation is allocated system resources,
like CPU time, disk resources, as well as network resources.
Now, with CloudKit last year, we wanted to give you a similar way
to be able to opt your lower priority CKOperations
into discretionary network behavior.
What we mean by that, is
that for your nonuser initiated tasks, for example,
pre-fetching content for the user like we just went
through by using CKRecords fetch record changes operation
in response to silent notifications.
You want those tasks to opt into discretionary behavior
so that the system waits for an opportune time
to perform those network requests.
The system takes a variety of factors into account
when deciding when to perform them, for exmple,
The system might wait for network connectivity
to improve before sending out those requests.
Also power conditions.
If a user is running low on battery
or the device is not currently charging, the system will wait
for power conditions to improve before sending those requests.
We did this by exposing the user background session property
With iOS 9 we saw an opportunity here to greatly simplify
and unify these things, these two concepts by using quality
of service to infer your network behavior,
and at the same time pull in everything else
that a given service level already indicates to the system.
So we are doing just that.
By deprecating the user's background session property
and recommending that you start setting quality of service
on all of your CKOperations.
Now, in the context of network behavior, you can set either
of the service levels user interactive or user initiated
to opt out of this discretionary behavior.
And for discretionary behavior,
you can either set the value utility
in which case we will try
to infer whether you should be opted into discretionary based
on whether the requesting app is foreground or not or background
which will always result in discretionary network behavior.
Please keep in mind that if you build your apps with iOS 9
and OS X El Capitan or later,
all new CKOperations will have the background quality
of service by default.
You should make sure that you audit all of your CKOperations,
take a look at what is the importance
of work that they represent.
Be a good systems citizen
and set the appropriate QS values on them.
NSOperation is very powerful API,
and there's a lot more you can do with it.
If you want to learn more, I highly recommend that you go
to the advanced NSOperation session tomorrow morning
In summary, I'd like to reiterate that error handling
for your CloudKit code is vital.
It is as important as any feature, and we would like you
to take a look, go back today and take a look
at all your operations, see what kinds
of errors have you been hitting,
and if you followed the general guidelines we talked
about today in handling them.
Number two, start batching your requests.
Whenever you see your app using the convenience API,
working on one item at a time and doing
that in multiple places,
think with about using the CKOperation version of that API
and batching those requests up.
You will not only improve the efficiency
that your operations execute with in the system in general,
you will also save your own network request quota.
Think about schema tradeoffs.
We've seen two cases where our schema tradeoffs came --
let us take advantage of optimizations.
For example when we added the thumbnail key
to our photo record, we were able to optimize our download
by just downloading the data that we need.
And in another case we were able to avoid an entire class
of errors when we avoided the same party record being modified
when photo records were stored on it.
So think about your schema carefully
when designing features.
And last but not the least, configure your CKOperations.
They have, they are a very powerful API
and they offer a ton of optimizations you can make
to the actual network request that gets sent
to the CloudKit servers.
For more information please check out our documentation
For all the other questions and answers, the technical support,
the forums and the CK support site are a great place.
For general queries, please e-mail CloudKit@Apple.com.
We have had some great related sessions this week.
I invite you to check them out when you go back today
to Learn all that's new with Web services
and what else is new in CloudKit.
We have one more Lab coming up tomorrow morning
at 9 in Frameworks lab D.
Bring your questions and we'd be happy to answer them.
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.