CloudKit keeps app data updated across iOS, macOS, watchOS, tvOS, and the web so you can focus on building your app. Hear best practices from the CloudKit engineering team about how to take advantage of the APIs and push notifications in order to provide your users with the best experience. Learn about the ways Apple apps use CloudKit and how you can apply the same approaches in your app.
Good morning. And thanks for checking out our session. My name is, Dave Browning. And here with me today is, Nihar Sharma. And we're going to be, we're both engineers on the CloudKit team. And we're going to be talking to you today about some best practices when building your applications with CloudKit.
So, what are we going to cover specifically? Well, first I'm going to walk through how we here at Apple use CloudKit.
We'll talk about the work flow and the API's that we use in order to provide a seamless experience for our customers. And by that I mean, allowing them to access the same data in their applications across all of their devices.
Next, Nihar is going to come up and talk about some details when using the CKOperation API. And the configurability and flexibility you can get when you use it.
Then he's going to talk about some things to consider when data modeling.
What you're going to store in CloudKit. How you're going to store it. How you're going to build your scheme up.
And then he'll talk about error handling, which we've always said is very important in CloudKit applications.
He's going to cover the different types of errors that you'll encounter potentially when using the API and how to consider responding to those depending on your application's use case.
So, a quick reminder, we here at Apple have built CloudKit, or built many applications on top of CloudKit.
And so you can be confident that yours will scale, because we've scaled it to hundreds of millions of users. I also want to do a quick refresher about the conceptual model.
So, the highest level, we have what's called a container. This is usually a one to one mapping to an application.
So, the photos application has a container in CloudKit, the notes application, your application.
Inside of the container you have a public database.
This is where you can store data that all users can see.
So, an example of this is the WWDC app, or the news app in iOS. They're built on CloudKit. They use a public database for storing articles, news, that kind of stuff that everyone can see.
Next there's the private database. This is where you store data specific to a single user.
That user can see their data across all of their devices, but no other users can see it.
And then new this year is the shared database. If you want to learn more about this makes sure to go back and check out the video for yesterday's session on What's New in CloudKit. And they do a deep dive on the sharing stuff.
Inside of the database you then have a zone. And there's a default zone, and a public, and private database, where if you throw records in there by default, that's where they'll end up.
But, you can choose in the private database to create 1 or more custom zones.
And you can store stuff in those zones.
And then when content is shared to a user from another user, that shows up as a shared zone in their share database, which you can think of as basically a proxy to the custom zone and the private database of the owner.
And then of course if multiple users share stuff with you in multiple custom zones you're going to end up potentially seeing a lot of shared zones in the shared database.
And now what I'm going to focus on with this work flow is mostly in a custom and shared zones that we're talking about right here.
Finally at the lowest level you have records.
This is your key value structure data where you store everything. And a record always exists in a specific zone.
So, a quick reminder on the benefits of using CloudKit at the high level, you focus on building your application and not worried about building back in services. We do that for you.
Your users get what we like to call automatic authentication.
That means, if they're signed into iCloud on a device, you do not need to prompt them for signing up, or logging in, or any of the stuff that can usually then be a barrier to using your application. If they're signed into iCloud with CloudKit, you immediately have a uniquely identifier for that user. And you can start storing data on their behalf.
And then finally, what the focus is today is you can get that same data, your users can get the same data across all of their devices when you store it in CloudKit.
All right. So, let's talk about the common use case that we see when we're building our apps on top of CloudKit.
And so what happens is, you have a user who runs an application, in this example let's talk about Notes. So, Notes is built on CloudKit.
They create a note on their iPhone, let's say, that's stored over to the cloud. And then when they open up Notes for the first time, maybe on a second device, their iPad, it feels like that data was sort of magically pushed. And it's already there in their iPad. And if they make edits in the iPad, it feels like it's just magically pushed the other way. So, that's sort of the use case that we're looking at providing for our users.
So, the way to think about this is, iCloud, the servers are the source of truth, your devices have a local cache of that truth data. And then CloudKit, the API is the glue in between the two. All right. So, how does this actually work? Well, basically when your app launches, the work flow we recommend is that you fetch changes from the server. Especially the first time your app launches, because you don't know if something already exists that the user wrote from another device.
All right. So, you talked to the server. You fetched down any data that you don't have yet. And then you insure that you're subscribed to future changes.
By subscribing to future changes, it tells CloudKit, the server, to send push notifications to your applications on their other devices.
And then of course when you get a push, you'll want to fetch changes again to pull down the new changes that happened from the users other device.
All right. So, let's dig into these in a bit more detail, so subscribing to changes. The way this work is, when your application is launched for the first time, and we'll talk about why in just a second, but when it's launched for the first time, you create a subscription telling the server, here is the set of data I care about subscribing to.
And then when that data changes, it will let you know.
When your application launches for the first time on another device the subscription might already exist for the user on the server. But, your app doesn't know that, because it's launching for the first time. So, you need to make sure it exists.
So, you'll do the same thing that you did before on the other device.
Now that you're subscribing to changes and CloudKit is sending you push notifications, you want to listen for those push notifications. So, let's walk through an example. The user writes a new note.
The application stores that down to the server. The servers says, ah ha, there's a subscription for this user. They wanted to know when new data showed up.
It says it came from the iPhone. So, I don't need to bug the iPhone. But, the user also had an iPad.
So, it looks up the proper Apple push notification service token. Talks to APNS on your behalf, and tells them to send a push to the iPad. So, you don't have to deal with sending pushes yourself in the back end. So, then finally your iPad gets the push. What does it do next? Well, as we said, now it goes and fetches the new changes from the server.
So, it talks to CloudKit, pulls down the new data, updates its local cache, and then when user opens up notes, bam, they see the same stuff they just wrote on their iPhone.
All right. So, let's look at actually building this. Let's walk through the code, the specific API that we use to make all of this happen.
So, first we'll talk about subscribing to changes.
So, remember what I said before. This is one of those things that you only need to do the first time your app launches. So, you'll notice we have a check here at the top of our code that says, have we locally cached on this device? The fact that we've already created this subscription, because if we've already created it, we don't need to keep doing it every time we launch. You save yourself network requests. You save the user some network stuff.
All right. So, if we haven't done this before, let's look in the code to set it up.
So, new this year in iOS 10, there's an API called CKDatabaseSubscription. This allows you to subscribe to any change across an entire database, and it works in a private database, in a shared database.
So, in this example let's focus on the new shared database.
So, you can give a subscription an ID, which allows you to check it later. And we'll talk about that it just a minute. But, in this case because we're dealing with a shared database let's call it, shared changes.
Next you need tell CloudKit what type of push you want it to send when this subscription triggers. And so let's dig into the different types you can do.
So, the first one that we actually use a lot and recommend in most cases is a silent push notification. And the way to do that in our API is on a subscription object. You can set the notification using a CKNotification info object.
And if you set the property, shouldSendContentAavailable to true, and only that property, you'll get a silent push, the back end will send a silent push on your behalf for this subscription.
And the key to this is that you do not need to prompt the user for acceptance. So, we've had people ask in the past, hey, we have this subscription, but it's popping up here. You guys have seen it, allow push notifications for this app. And a lot of people hit no to that.
You don't get pushed and then you rely on pulling.
If you do it this way, you're not going to be alerting the user in any way. So, you don't need to ask for acceptance.
And then finally you can listen for these notifications in your app delegate. RegisterForRemoteNotifications.
So, if you do want to send a UI push, you actually want a badge, or banner, or make a sound, then you can set any one of these 3 properties.
And that will tell CloudKit to send a UI push to your user, because you will be alerting them you do need to ask for user acceptance in this case. And then of course you register for remote notification in the same way as before.
So, a little bit of fine print. If you read the APNS documentation, they tell you that pushes can be coalesced depending on the conditions of the device. So, that means if a subscription triggers and a push is sent to your user's device, there are many cases low battery, bad network, etcetera where they may not get that push.
But the coalescing piece means they promise to deliver at least one of those.
So, what that means is, you should not think of push as a way to tell your apps what changed, because if 5 pushes were sent but you missed 4 of them, you're going to miss 4 of those what's.
Instead, think of push as a way to tell you that something changed. One or more things. And that's why we need to go talk to the server, to figure out what those were. And the good news is the CloudKit API provides you a way to ask for only what has changed. So, you don't have to pull down all the stuff you already have.
All right. Let's jump back into our code. Remember we're creating a subscription. And in this case we've set up a silent push notification by doing shouldSendContentAvailable to true.
Now, we need to take this subscription and ask the CloudKit client to save if off to the server. And you're probably familiar with this if you've used CloudKit. But, the way you do everything is with operations. And in this example, we do a CKModifySubscriptionsOperation.
And we tell it about the subscription we've just created. And this is the one we want to save.
With CloudKit because the client is going to be talking to the server over the network, and that might take a bit of time, everything is asynchronous. So, that means all of your operations have completion blocks.
They get called once a response comes back. And so in this case, we have a modify subscriptions completion block.
And the first thing you want to do in all of your completion blocks, I know we say this all the time, is check for errors.
And Nihar is going to talk to you in a bit more detail about this.
But, in this case, let's just say if there not an error, then we know that the subscription was saved. Now we can locally cache the fact that we've done this, so that we don't do it next time, because of that check up at the top. Quick note, and Nihar is going to talk about this to. CKOperations inherit from NSOperation, or Operation in Swift 3, and so you have the ability to set this quality of service property. And the default is utility. And he'll talk about some of the things to keep in mind when setting this.
And then finally, the way to actually finally take this operation and ask the client to send it back, is to add it to a database's operation queue. And again because we're doing a shared database subscription in this case, we add it to the shared database's operation queue. And the client will now go off and send that to the server.
Okay. So, we're subscribed to changes now.
So, now the next step is to listen for pushes, since CloudKit will send them to us when data is changed on another device. So, you want to make sure that in Xcode you've turned on background modes.
And you want to check the box for remote notifications. And potentially background fetch if you want to do this while your app is in the background.
Once you've done that, there's a method in the app delegate that you might be familiar with, which is applicationDidReceive RemoteNotification, fetchCompletionHandler.
This method gets passed a user info dictionary.
And CloudKit provides you a handy way to see if you can get a CKNotification out of that dictionary.
So, if you can, remember there might be pushes for other reasons. But if it's coming from a CloudKit subscription, you'll be able to get a CKNotification. And now we can check the subscription ID.
So, the reason this is important is because likely you're going to end up having a database subscription for the private database, and the shared database and maybe for other things. So, this is how you differentiate which subscription triggered this push.
So, in this case let's say it was our shared changes.
Now we can go off and fetch that shared data. And we'll talk about that in just a second. And when that's done, we call it CompletionHandler that was passed into this method.
Okay. So, now we are listening for pushes.
And let's say some time later your application gets a push.
So, what do we do? Remember what we said. You need to fetch new changes.
And so let's look at a graphical example of sort of how this works.
So, we got to push into our device. And we said, okay, that's for the shared database.
There are sort of 2 steps you need to take.
The first one is, you go and ask the server for which zones changed in the shared database. Remember we talked about zones before, right, because there may be new zones that showed up that you don't even know about, because someone shared something with you.
And you pass along a server change token that tells the server where in history you are. And we'll dig into that in just a minute.
Step 2 is, now with the no zones, you go back to the server and say, okay, please tell me what records where changed inside of those specific zones.
And again you have change tokens for each zone marking where in history your local device cache is.
All right. So, let's talk about that change token thing in a bit more detail.
So, imagine your user has a device, the iPhone we were talking about before, they're using your application, and they send some changes down to the server.
Those changes are accepted, and the server wants to mark that point in history. It does that with the server change token. And in this case for simplicity sake, let's save the letter A. That device sends down another set of changes that are accepted, goes to B, etcetera, it goes to C.
Sometime later user runs your app on a second device. Let's say that iPad we were talking about before.
And the first thing that iPad needs to do, remember on app launch is go and ask for any changes that it doesn't have.
So, it goes and says, I would like to fetch the changes from the server. The server says, okay, here you go. Here's what exists. And at the end of that it says, you are now at server change token C. So device 2 has now marked where in history it is.
Now let's say device 2 then later writes some data. Somewhere along the way device 1 gets a push.
Device 1 fetches down the new changes. And at the end of that the server says, okay, you're now at change token E. All right.
Device 2 makes another set of changes. Device 1 gets a push, pulls that stuff down, it's now at change token I.
Now you'll notice that device 2 is still at change token C.
And that's because when you're writing data you're not getting change tokens back. You don't get those until you actually fetch it.
So, let's say device 2, the app restarts, or they restart their iPad or something. Your app launches up. You go and fetch changes. The server will send you the stuff that you wrote from that device, because it doesn't know if you had that locally cached or not, for various reasons. All right.
So, don't be surprised if you see some of the same. This allows you to confirm that you have this stuff. And now at the end of that, the server will send out a change token, I. And you notice that both devices are now at the same state. They have same server change token. So, that's sort of an example of how that works.
Okay. So, let's look at actually writing the code for fetching these changes.
So, in this case we're going to talk about database. So, a new API, iOS 10 is CKFetchDatabaseChangesOperation.
And again you pass in the server change token that we just talked about. And the first time you ever do that, that will be nil, and it will tell the server that you have nothing. And it will give you everything that exists on the server.
Next, in previous versions of the API, at the end of this operation you had to actually check a flag to see if the server said that there was more data coming. Now, we'll talk about it in just a second.
But, if that was set to true, it was your job on the client's side to re-trigger this operation. But, what we've done is, we've added a new property on these operations called fetchAllChanges. And it defaults to true. And what it does is, it tells the CloudKit client to do that on your behalf so that you don't have to fool with it.
So, if after running one of these operations, if the client sees that the server has more data, it will automatically enqueue your operation again for you. And of course call your callbacks along the way so that you don't have to worry about that anymore.
Next, you want to implement this completion block, recordZoneWithIDChangeBlock.
And this is where you will be told about the zones that changed in the shared database.
So, you'll want to collect these zone ID's, and we'll talk about what to do with them in a second.
Next, there is, recordZoneWithIDWasDeletedBlock.
This tells you about the zones that no longer exist on the server. And allows you to clean up any local cache data that was in those zones from before.
And you'll see this in cases potentially where something was unshared with your user, because that zone no longer exists in their shared database. And then new is, a changedTokenUpdatedBlock. So, let's dig into this.
So, remember before we had device 1 had written some data down in the server.
We had device 2 had written some data down. And then sometime way later, device 3 for this user comes along. And remember the first thing it needs to do is, go talk to the server and fetch any changes.
And because fetchAllChanges was true, it's asking for everything.
But, there might be a lot of data there, like in this case. Right. And the server might decide, it doesn't make sense to send all of this back in a single response. So, we're to chunk it up.
So, it's going to send you back a chunk of it.
The CloudKit client will see that there's more coming from the server. So, it needs to go back and issue another operation on your behalf. But, before it does that, it's going to call changeTokenUpdated and tell you that you're now at change token C.
And this allows you to sort of move along with the client as it's making its operations. So, you update your local change token and don't repeat some of the stuff you've already done.
So, let's say this client, on your behalf, says, there's more stuff coming from the server. Let's go back and get it.
See some new data. At the end of that calls changeTokenUpdated, see if there's new data, goes back to the server again. But, this time there's an error.
In your error handling, it's likely that you're going to end up calling fetchChanges again.
But, instead of starting all the way back at A, because you have a changeTokenUpdatedBlock, your local change token is now at E. So, when you call your next one, the server says, okay, you only need F through I. You don't need the old stuff that you've already processed.
So, that's why it's important to implement the changeTokenUpdatedBlock. And you just cache the server change token just like you normally do.
And then finally we have our completion block, which in this case is, fetchDatabaseChanges CompletionBlock.
Again, the first thing you do, error handling. Again, Nihar will walk about this.
And then you'll get a final change token, which in that last example had we not received an error, would have been the I at the end.
Cache that just like you normally do.
And so now we've collected a set of zones that changed in that database. Now we need to go fetch the records that changed. That was step 2 in that diagram before.
And we do that via new API, CKFetchRecordZone ChangesOperation.
And it allows you to pass in a set of zone ID's. So, you don't have to worry about calling all these for all the different zones that changed. You call one, pass in all the zones that changed.
And we won't dig into the code, but it looks very much like this. You're going to have some completion blocks where you're dealing with records instead of zones. And you'll be getting change tokens along the way. All right. So, quick recap on what we talked about.
So, you subscribed to changes to tell CloudKit that you want to receive pushes when a user changes the data on another device.
You've listened to those push notifications. And when you receive one, you've gone and talked to the server, and fetched exactly what changed. And by doing this, your application now provides that seamless experience for your users across all of their devices. All right. So, now Nihar's going to come up and dig into some more specific best practices.
Good morning, everyone. Thanks for coming out today. My name is, Nihar Sharma. And I'm an engineer on the CloudKit team.
And today I'm very excited to share with you some CloudKit best practices that we've learned here at Apple over the past few years that you can take advantage of in your apps today.
Let's take a look at what we're going to cover. First, I'd like to talk about automatic authentication.
Dave touched on this briefly. I'd like to go into a little bit more detail about what it is. How you can take advantage of it. Next, I'd like to talk about the CKOperation API, which is the workhorse of our native talk at frameworks.
Then we'll dig into a couple of tips that you can keep in mind when designing the schema for your apps, that will help you take advantage of the CloudKit API more effectively.
And finally, we've told you before just how critical great error handling is to writing a CloudKit app. And I'd like to reiterate that today.
And talk to you about a few different class of errors, and how your application should handle them.
So, let's get started.
First up, automatic authentication.
Now, you might be familiar with a UI like this, where on first launch an app requests a lot of private information from a user, even before the user has started using their apps.
Well, we think in CloudKit we have a great way for you to increase the chances of engaging with your users without requiring any private information upfront.
The way we do this is via the CloudKit user record.
As a reminder, this user record is automatically created for every user that when they first use your apps that is logged into an iCloud account.
In this manner it is unique per CloudKit container.
And it offers you a stable identifier for that user that is stable across app re-launchers, OS upgrades, etcetera. So, you can save this identifier to your servers and start building a profile for that user right away. And when you notice that they're engaged with your apps a lot more, then go ahead and request information to enrich their profiles.
The way you access the user record ID for the current user is via the fetchUserRecordID CompletionHandler API on CKContainer.
So, that was automatic authentication with CloudKit.
Now, let's talk about CKOperations.
As a recap, there are 2 main ways in which the CloudKit framework exposes operations to your apps.
One is via convenience API calls, which work on one item at a time.
And the other is via those CKOperation counterparts. So, in this manner, every convenience API call that we expose, that works on one item, has a CKOperation counterpart.
And that works on a batch of those items. So, for example, we have the fetchWithRecordID API on CKDatabase to fetch 1 record at a time.
And we have its corresponding CKFetchRecordsOperation that takes an array of record ID's and fetches them in a batch. Now, there's certain advantages to using this CKOperation API that you get over convenience API call. I'd like to walk through a lot of those today. So, first and foremost, CKOperation is a subclass of NSOperation.
What this means is that, you get the full power of the NSOperation API available to you for free.
You can do things like, set up dependencies between your CKOperations.
You can assign the quality of service to them, to let the system know how important that operation is to you, or even manage queue priorities when scheduling them on your own NSOperation queues.
And you even get cancellation for CKOperations that have already started executing.
So, I'd like you to go up, go back, and read up the documentation on NSOperations to take full advantage of the CKOperation API.
A great reference for this is our advanced NSOperations talk that we gave at WWDC last year.
So, now since we're talking about CloudKit operations, there are a few more things that come along with the ride.
And I'd like to touch upon 3 of those main things today.
First, is the configurability that CKOperation gives you.
Next is, how it lets you optimize resources both for the system, and you as a developer.
And last, I'd like to talk about lifetime management, which is something new that we've enabled you to be able to do in iOS 9.3.
So, first up, a quick recap on just what you can configure on a CKOperation.
Operations let you have fine grain access to whether or not you wish network activity for that operation to go out over cellular.
You can also specify keys for operations that fetch items from the server if you want to download just partial records instead of the entire record, which the community's API does not let you do. You can even limit the number of results that a particular operation returns to you.
And finally, long running operations also give you progress updates that you can use to drive certain UI elements. Now, let's talk about resource optimization.
The number 1 resource used by your operations on the system are network requests.
Now, every convenience API call turns into at least 1 network request on the system. So, when you use the CKOperation batch API, you allow the system to minimize the number of requests needed to be able to send your changes to the server.
So, for instance, if you want to save a batch of records and you use a CKModifiedRecordsOperation, the system will take that batch and be able to optimize a request needed to send that to the server. So, now in this manner it is not only good for the system resources, but it also helps you optimize your network request quota as developers. Additionally, by default CKOperation lets you opt your network activity into discretionary behavior.
What we mean by this is that, you let the system decide an opportune time for your request to be scheduled.
For more information I encourage you to check out the discretionary property on NSURL session Configuration.
The way we expose this to you on CKOperation is via the quality of service properties.
So, by default, CKOperations have a quality of service utility. Any QOS, which is utility or below will opt-in to this discretionary behavior. So, if you notice that your CKOperations are taking a much longer time to execute than you expect, that might be because the system does not think that it's a good time for your request to go out yet. Now, additionally there are a few other behaviors that you should keep in mind.
When opting into discretionary behavior, network failures will be automatically retried for you.
And along with that, by default you get a 7-day resource timeout for every single request that your operation executes.
So, that was resource optimization.
Now, let's talk about CKOperation lifeline management.
On our platforms, there are various reasons why your application may exit.
For instance, you might be suspended in the background and be evicted, or a user might force quit your app.
Now, you might have certain updates running while this happened, which were either user-initiated and you really want to just save them to the server. Regardless of whether your app may have exited, or you might have a longer running update at a lower priority that you wish to finish whether or not your app sticks around.
So, to serve this purpose in iOS 9.3 we introduced the concept of CloudKit long-lived operations.
What these operations let you do is, once you mark them long-lived, the system will execute that operation on your apps behalf whether or not your app sticks around.
We will cache any server responses we get for that operation, and will provide you with an API to be able to replay those responses once your app returns.
So, let's take a look at the API that we have exposed to be able to do this.
Well, on CKOperation it's pretty straightforward.
You have an isLongLived flag. So, you create a CKOperation like normal. Once you've set this flag, you've turned it into a long-lived operation.
Along with that you have an operation ID, which is a system assigned string that uniquely identifies every CKOperation.
And we will take a look at why that's important in just a second. So, here's the general architecture of how you run a long-lived operation.
You initialize an operation like you would normally. You just set the isLongLived flag on it, along with your arguments and callbacks, and you run the operation.
Now, when you wish to resume it, you will fetch that long-lived operation from the CKContainer class via the operation ID. You will set the callbacks if you're interested in hearing about.
And you would run that operation once more.
Let's take a look at an example.
Let's say we wish to run a long-lived fetchRecordsOperation.
So, we set up the operation like we did normally with our arguments. But, we remember to set the isLongLived flag.
And we save the operation ID property to our local cache so that we'd remember what this operation represented. We set up the callbacks, and then queue the operation. Now, when you wish to resume it, you can use the fetchLongLivedOperation with ID API available on CKContainer to fetch that operation.
And since you know what that operation represents and from your cache, you can safely cast it and set the appropriate callbacks on it.
You do not need to set the arguments again, or tweak any of the other operation properties that you might have previously, and you've resumed that operation.
Now, CloudKit will then replay all the cache responses we have back for that operation.
And either catch you up to the progress that the operation has made why your app was not around, or give you the entirety of the results of that operation. And what this also means is that these operations are cleaned up. That's important sign.
And normally this happens when the completion block of that operation is called. You must note that your apps will have at least 24 hours to resume any long-lived operations that they might have enqueued.
So, that was all about CKOperation API.
Now, let's switch gears and talk about data modeling.
There's 3 main tips that I'd like to give you today.
The first one is around schema redundancies. How you can use them and how they help you leverage the CloudKit API more effectively.
The second one is how to use references to avoid a certain class of errors that you might encounter in your CloudKit apps.
And lastly I'd like to talk about parent references, which are a new type of reference that we've added in this release to support CloudKit sharing.
So, let's talk about schema redundancies.
Let's take an example of developing a photo sharing app.
Now, the first thing that you might have on a server this app is a record type for photo, where you store a user's high resolution photograph that they've taken on one of our iOS devices as a CK asset. Now, let's say when the user first launches the app, while you're fetching changes like Dave recommended before, you wish to display just a thumbnail view of all the user's most recent photos. Something like this, which the photos app does.
Then it seems like an awful waste of network bandwidth to be able to download that entire high resolution asset every single time you have to load this page.
So, what can you do here? Well, it's pretty straightforward.
You can think about adding a redundant field on that record, which represents the downscaled asset.
And what this allows you to do is, coupled with the usage of CKOperation and the desired keys property that we talked about, that enables you to fetch partial records. You can now fetch just the key that you need to drive the UI that your user's interested in.
You can even set the results limit property to limit just the number of results that you're showing on a single page. So, in this manner, you can enqueue an optimized download to be able to present a dynamic UI to your users.
You fetch only what is needed, when it is needed.
And the desired keys property is also available on the new CKFetchRecordZone ChangesOperation, as well as CKFetchRecordsOperation, both of which fetch items from the Cloud for you.
Now, as a reminder, these API's are also available to you on the web via CloudKit JS.
So, by using them, you enable your users to have a more dynamic experience in your apps, because they're not waiting for you to finish operations that are downloading data that they're not going to use. Next, let's talk about CKReferences.
As a quick reminder, what are these? Well, they're CloudKit's way for you to point records to other records.
So, for instance, if we have 2 records here. Record A and record B, we store reference on record A, initializing it with record B.
We've created a CKReference. Now, let's say we wish to add albums to our photo sharing app, which can contain multiple photographs. So, how would we go about modeling this one-to-many relationship? Well, in a simple manner, and either you might think that this might be a good day to model for this.
Storing the record ID's, the references to the photo records that are part of that album right on the album record itself as an array.
This is what that will look like. Now, let's take a look at what happens when multiple devices for your user try to add albums, try to add photographs to that same album.
So, let's say we have an album record up on the Cloud that does not have any photos yet.
Now, remember that your users are going to have multiple devices.
They all fetch this album record.
See that it has no photos. They're happy with it.
Now they have photos. They each have photos that they wish to add to the same album.
So, now they in-queue that update on that record.
Let's say our iPhone gets there first. And now the server knows about one of the photographs, the references to one of those photographs.
But, now when the other new devices come in and try to make their updates, they're no longer updating the latest version of the server record.
So, in this case, both of those devices will see the error, CK error serverRecordChanged.
Now, I invite you to take a look at our advanced CloudKit talk from WWDC '14 for more details on how you can handle this error.
But, let's see if we can figure out a way where we can avoid it entirely.
Well, in this case we know that our album record is going to have frequent rights on it.
So, instead if we model our one-to-many relationship using a back pointer by storing the reference on the photo record that is part of that album, we completely eliminate the right contention that we had on our album record.
So, in this case whenever a new photo needs to be added, you just set a reference on it to the album record that it's a part of and save the photo record. But, by doing this you've completely eliminated your contention that you had previously.
And now you might wonder, well, how do I fetch all those photos, which is part of my problem to begin with? Well, you can use queries for this purpose.
Here's a query that you need to be able to fetch all photo records that are part of this album. It's pretty straight forward. All you need to do is equate it to the album reference field that you have on your photo records.
Next let's talk about parent references.
On CKRecord this year, we've added a new type of reference called a parent reference that will help you model your data in a way that better supports CloudKit sharing.
What we'd like to recommend is that, if your app supports sharing, you use the parent references to set up a hierarchical data model.
Recognize the unit of sharing in your apps and set up the parent references accordingly. Let's take a look at an example of what I mean by that. Our photo and album records are a great example of using a parent reference.
An album is clearly a parent of a photo record.
So, all we need to do is set the parent property on the photo record to our album record ID, and save that photo record.
Now, let's say that we've used this model throughout our app, and we end up with a hierarchy that looks like this.
And now a user comes along that wishes to share this entire album.
Well, all you would need to do in this case is, create a CKShare with that album record as the group record.
And in one fail swoop you will share the entire hierarchy that you set up using parent references.
Now, CloudKit also supports partially shared hierarchies.
So, in this case, if a user might have only wanted to share photo C, for example.
You could have created a share just with photo C.
And in this case, only the photo C and all its descendents set up via parent references will be part of that share.
And that was data modeling.
Let's talk about error handling.
Now, there are a few different types of errors and your application should handle these in a few main different ways. And let's take a look at each of them.
So, let's say we have a simple example where your device comes along and you chose a CKModifyRecordsOperation and tries to talk to the server.
There are 2 main things that the server might respond with.
It could either say that, I didn't like that request at all, please don't try it again, or it could say that everything was fine with your request, but right now is not a good time.
Come back a little later and try it again.
Now, we need your apps to handle these 2 types of errors in very different ways. And let's take a look at what that is.
The first kind of error, which is a fatal error, you really can't do much.
There are a couple of error codes which indicate a fatal error. For example, internalError, serverRejectedRequest, invalidArguments, or permissionFailure.
In this case, we want you to show an appropriate UI to your user in your apps and let them know that something went wrong, which cannot be retried. However, the other type of error, where the server wants you to come back at a later point and time, we will tell you the amount of time that the server wishes for you to wait.
Here are a couple of error codes that will have that value of time embedded in them. ZoneBusy, serviceUnavailable, and requestRateLimited.
When you receive any of these error codes, you should check for the CKError RetryAfterKey in the errors users info dictionary. You should wait for that period of time and reinitialize the same CKOperation with the same arguments, and retry that operation.
Here's all the code you need for a simple example of how to wait for that period of time represented by the CKErrorRetryAfterValue.
And reinitialize the same CKOperation.
Now, what happens in some cases when your operations might be failing on the device before even talking to the server? There are 2 main cases that I'd like to discuss today where CloudKit may be completely unavailable to you.
The first one is when the device is off line. Well, in this case, what we recommend is that you monitor network reach-ability just like you would for any other network based app.
If you're using a quality of service of user initiated or above, such that a network failures are not being automatically retried for you, you will see this error code CKErrorNetworkUnavailable.
So, once you're monitoring network reachability, which you can do via the SCNetworkReachability API, for example, in system configuration framework, you can then let the user know that, hey, these changes are not going to make it to the server yet.
But, we recommend that you let your users keep interacting with your app while the device is offline.
You should save these changes to where your local cache.
And when your reachability API tells you the device is back online, then you enqueue your CloudKit operations to save those records to the server.
The other main state is when you're trying to use the private database for a user and the user is not logged into an iCloud account.
In this case we will return the error code CKErrorNotAuthenticatedTo.
And what we recommend is, for all of your apps, on first launch always register to listen to the CKAccount change notification.
When it fires use the account status with CompletionHandler API to research the account status for the current user.
And let them know that certain operations may fail, because they don't have an iCloud account signed in.
So, in summary, let's take a look at what we've seen today. We saw how to subscribe and fetch changes to efficiently stay up to date with the server.
We saw the advantages of using the batch CKOperation API, which we recommend all of your apps adopt. We saw a couple of tips on how to design your schema to completely avoid a certain class of errors, or to use the CloudKit API more effectively.
And finally, we saw how to handle certain types of errors. And how to differentiate between them. And what the server means when it comes back with certain error code as opposed to others. Now, we had a related session yesterday. If you could not check it out, I invite you to go back and look at it online.
More information is available here. Thank you and have a great day.
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.