Essentials • iOS, OS X • 53:07
Core Data is a powerful way for your app to store data in iCloud. Dive into a step by step, real-world primer for adopting iCloud in your Core Data app, from configuring your persistent store for iCloud, to migrating existing content, and how to have your app behave throughout its lifecycle. A must attend session for any developer interested in Core Data and iCloud.
Speaker: Adam Swift
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good afternoon. My name's Adam Swift. I'm a senior software engineer in the Core Data team, and I'd like to welcome you for the Using iCloud with Core Data session. In this session, you will learn how to take what's really a relatively simple set of APIs to build a robust and well-behaving iCloud Core Data application. We're going to take you through the steps necessary to go from an idea to a fully functional app. And we're going to give you sample code to help illustrate-- You're welcome.
We're going to give you sample code to help illustrate the strategies and the points that we're making throughout this session. And in fact, we're going to try and tie in as much as possible the sample code into the session, and that will be made available to you through the conference attendee site. If you look up the session, you should find a link to the sample code there.
Alternatively, you can also look it up by name. It's the shared Core Data sample. So you shouldn't have to write down any of the code samples you see on stage throughout this presentation. They'll all be available to you with references into that sample code that you can download.
So before we get into using Core Data in iCloud, I wanted to take a minute to ask for a little bit of audience participation. We were curious to try and understand how many of you think or would describe yourselves as an experienced Core Data user at this point? That's pretty good.
Okay. And how many of you would describe yourselves as only having looked at Core Data or maybe are considering using it in a new application? Okay. All right. Well, I wanted to just quickly, before we get into using Core Data with iCloud, talk about the technologies that we're discussing today. And Core Data is an application technology that helps you manage your application data. So in the context of this session, the data that we're talking about is stored in an SQLite database. And in the terminology of Core Data, we call that a persistent store.
When you pull data from that database using Core Data, we talk about those records as managed objects, and you work with them in a managed object context. When you've made your changes in the application's managed object context and saved them, Core Data takes care of putting those records back into the persistent store or database.
Now, what do you get when you take Core Data and you add iCloud? Well, it means instead of your data just living on the file system in a Deskulite database, that data is made available through iCloud to all the iCloud connected devices. So changes you make on your iPhone and save are available on an iMac, and changes on the iMac are then made available to the iPhone as well.
And the specific features that you get with Core Data and iCloud are significant. The big one that I want to call out here is per record conflict resolution. And what that means is that if you make changes between one device and another on a single record, Core Data does the work to reconcile what changes belong at the end, merging those changes together to be consistent.
Adam Swift And I'm going to illustrate what that means a little bit here. So we've got a record here, John Doe and [email protected] is the person data. Adam Swift And then on an iPhone, I may go ahead and change the first name to Johnny. And then later I may go ahead and change the last name, strangely, to Appleseed.
Meanwhile, on my iMac, which doesn't have its Ethernet connection right now, I go ahead and change the email address for John Doe to [email protected]. Well, when all these devices are able to connect up on the network again, that record will be reconciled with those changes automatically by Core Data, using the information from iCloud, without any extra work from you.
Some other features you get with iCloud and Core Data is Core Data does a lot of work to only transfer the smallest amount of data necessary to iCloud, minimizing the amount of network bandwidth when you make a change. It doesn't upload all of the data that you're storing in your database, just the parts that changed. And those changes are asynchronously imported in the background on your other iCloud-connected devices.
So you don't have to do any work to import those changes. We take care of it for you, merging in the per-record conflict resolutions. And last but not least, we also support lightweight schema migration with iCloud. So that means if you've got one device running an older version of your app that's connected to iCloud, and a newer device, or another device connected to a newer version of your app, with a new schema, those changes, because they're based on different schema data models, they can't be combined at that time. But when all the devices are upgraded to a consistent version, we will then migrate the changes that live in iCloud to bring all that data together, so those changes won't be lost even through that schema migration.
And to use Core Data with iCloud, you need the standard sets of tools you need to develop an application using iCloud. Xcode and the iOS or OS X SDKs. And then you can use the Core Data project template or start with the sample code that we're providing you today. And then you want to enable the iCloud entitlements for your project. You also need to sign up through the provisioning portal to get an app ID registered for iCloud, and get a provisioning profile for any devices you want to run your application on.
All right, with that, I'd like to get into talking about using iCloud with Core Data. And we've broken this up into three main focus areas. The first is your application design, designing your application to work well with iCloud and Core Data. The second is the application launch experience. And then last, I'm going to turn it over to Nick, who's going to talk about working with your application in the application lifecycle.
So let's start off with the application design. And when we're talking about application design, especially keeping in mind iCloud, you want to think about what your users' expectations are. Users love iCloud. It means they can work with their data on their iMac and then go on the road with their iPhone, and that same data is available to them, and they can continue to work with it, and then it'll be available back on their Mac when they get home again. So your users kind of want to have all that data shared through iCloud available to them.
But you also want to consider some of the practical issues of what kind of network you're going to be connecting over. What if your user isn't signed into iCloud, or what if they switch to a different iCloud account? And also, just how much data are you really going to accumulate in iCloud? And you don't want to overrun that and lead to a bad user experience.
So we want to talk about how to divide up your data so that the data that's important to be available to your users across all their devices is made available through iCloud. But maybe some of that data can be derived or recalculated. Or maybe it's changing with such a high frequency that you don't want to clog up the network connection with lots of changes all the time that really aren't preserved. So the general approach that we would suggest is using one iCloud enabled store for data that can be shared, and then using a local store for data that can be recalculated or just belongs on the device because of the nature of that data.
So I've talked about putting your data into iCloud, but what does that really mean? What am I talking about there? Not your persistent store file. The problem is, the persistent store file in this case is an SQLite database. And SQLite uses fine-grained file system controls to control access so that it maintains internal consistency with your data.
It does a very good job of making sure that it doesn't get corrupted and things like that. But this is not the type of file system access that iCloud is designed to handle. Core Data uses a strategy of transferring the incremental changes, or something we'll also refer to as transaction logs, to iCloud that represent the data in your persistent store.
So you can also think about your persistent store file, when it is enabled for iCloud, as a cache that represents the data that's being stored in iCloud. You're going to access your data through the persistent store file. You don't access the data in iCloud directly. And one of the nice features of that is that at any point in time, you can recreate that persistent store file from the data that's available in iCloud. And I'll refer to that later in another section.
So what about the case where you want to enable iCloud for one of your stores, but the user hasn't signed in or signed up for iCloud? Well, in that case, we're going to talk about this as a fallback store. When your user hasn't signed up for an iCloud account, you want to still be able to provide a seamless app experience so your user can develop or can work with data and utilize your application, but that data won't be uploaded or transferred to iCloud until they sign up for an account or sign into their account. We call this a fallback store because it's slightly different than the local store that's designed for data that just belongs on your device. The fallback store is really representing data you want to put into iCloud, but the user hasn't made iCloud available to you yet.
This is sort of the same position you'll be in if you have an existing application that utilizes Core Data, but does not yet utilize iCloud. Your users may have developed a significant body of data in this application, but since your application hadn't enabled access to iCloud on the device, you'll probably want to make that data available to iCloud, and that's where it's the common experience. At some point in time, when the user signs into iCloud or when your app makes iCloud available to them, you want to move that store data to the iCloud-enabled store.
So for the case of the iCloud-enabled store, when your user has signed into iCloud, You need to choose a place where that persistent store file will live on the file system. And that's a decision you need to make for your application, based on the access needs of your application.
Do you want to make that data only accessible when the user is signed into iCloud? Or do you want to let the user access that data even after they've signed out of iCloud, but only in a read-only manner? So if it's the case that you only want to let the user access the data when they're signed into iCloud, then what we would recommend is put the store in the iCloud container.
But wait, you're going to say. You just told me two slides ago. I should never do that. It'll destroy the world. Well, this is a special case where if you create a folder called, say, myfolder.nosync, something with .nosync in the name, in the iCloud container, that's a special rule that means that that folder and its contents will not be transferred to iCloud. So why would you want to do this? Well, it makes it easy to make sure that your store file is only going to be associated with the data that you've stored in iCloud for that store file.
If the user switches accounts, the container is removed and the store file goes with it. Remember, you can always rebuild the store file from the Cloud data, so the data isn't lost. You just need to repopulate it after that switch. And the main feature of this, as far as we're concerned, is that it's simple.
You're guaranteed that the persistent store file that you have, that you're working with for that data, for that user's iCloud account, will only be accessible when they're signed into that iCloud account. You don't need to worry about mixing up your persistent store file with a different iCloud account's data that's been created from a store.
So the alternative, though, is to put the persistent store file in the application sandbox. You're still going to write your data to iCloud, But it means that even if the user is signed out of iCloud, that persistent store file will survive account changes, like sign outs or switching to a different account. But what it does is it shifts some burden to you to make sure that you only make any access in terms of changes to that persistent store file when you're able to connect to that same iCloud container.
You can still use that persistent store file in a read-only manner by using the NSReadOnlyPersistentStore option to access that data in a read-only manner if they're signed out or signed into a different account. And you can even move that data into a fallback store if they're signed out, where you can then work with the data locally.
So I've talked about a couple different scenarios with persistent stores, and I think I mentioned it before, but The focus here is, going back to where I started, talking about what data do you want to store locally on the device that really only belongs on the device, and what store do you want to put into iCloud so it's available across devices? How do you want to partition this data? Well, you need to do it by assigning one store to be local and one store to be iCloud enabled.
You can have more than one, but fundamentally, it's per store you make this decision. And there's a couple different ways to achieve that division of data. One is you could use different models and different coordinators, but the other one that we're going to talk about for a minute here is to use different data model configurations.
So what is a data model configuration? Well, it gives you a way to carve out a subset of the entities in your data model and say, I want to work with those entities in my iCloud enabled store, and I want to work with these entities in my local data store. So it's a convenient way to be able to work with a single data model that combines all of these entities, but identify certain entities for use with one store versus another.
One of the big benefits here is that you can then, with a single managed object context, access data from both of those store files using different configurations and bring them into the same context. And then when your user interface decides it's time to save, we'll save out those records to the appropriate store according to the configuration that they're associated with.
So how do you create a configuration? You do it in the Xcode data modeling tool. And you can see here, here's the data model from the sample code. And I've created two entities, a person and a state location. So in this case, I want the person entity to be shared across devices. I wanted that to go into my iCloud enabled store.
But the state location, we're actually just going to bundle the 50 states as read-only data with our application. So that's going to stay in the local store. There's no reason to share that through iCloud. So how do I create a configuration? I use this pull-down menu here to add a configuration.
And then I name my one configuration Cloud-- not clown, Cloud Config, and I put my person entity into that. Then I create a second configuration called Local Config, and I put my state location into that one. Then to actually create these stores in code, I use the addPersistentStore method on my persistent store coordinator, and specify the cloud configuration for my cloud store, and specify the local configuration for the local store.
So in summary, the focus on application design should be on what do your users expect to be able to work with across their devices? What data belongs in iCloud versus what data belongs in the local store? Think about where that data should go, considering the limitations of maybe a limited network at times, or users who haven't signed into iCloud, or may switch accounts for different reasons. And last, think about the trade-offs between simplicity and additional functionality in terms of where you put your iCloud-enabled store on the file system.
All right, now I'd like to switch gears and talk about application launch. An application launch seems like a simple thing, not a lot of complexity to it. But you need to consider that there may be a lot of different scenarios under which your application is launched. This may be the first launch from a new user of your application on a device, and you may want to seed some data into your iCloud enabled store. Or maybe it's a new device, but your user has been using your application for a long time on another device, so there's a lot of existing data that you want to make sure is presented to the user when they launch your application.
So the goals here are you want to get to a full usable user interface for your application, and you want to evaluate the status of what's going on at launch time. Is everything ready to go? Is all the data present on the file system already and all you need to do is present it to the user? Or is this the first launch and you need to seed? Or has this user not signed into iCloud and now you need to fall back to keeping your iCloud enabled data in a fallback store until they do sign in? And throughout all of this, you want to make sure your application stays responsive, so your user interface isn't waiting while you're figuring out the results of these questions.
So let's walk through the launch steps. User launches your app. You present an initial UI to your user. And then you need to make that decision. Check the iCloud status. Is the user signed in? If they are, then you want to kick off a background queue. So you do this work in the background to add your iCloud-enabled store, maybe seed some data, and then when everything's ready to go, notify your user interface that this data's ready to be displayed to the user.
On the other hand, what if the user is not signed in? What if there isn't an iCloud account available? Well, again, in this case, we're going to want to kick off a background queue to do this work, because we don't know how much work we have to do yet. We'll want to add a fallback store, maybe. We might want to do some work on that fallback store. And again, when we're done doing all that setup, notify the user interface that it's time to refresh. All that data is available now.
So, now I'd like to walk you through the code that's involved in evaluating those steps. And you can see in the top corner of the screen here, we've got a little bookmark that indicates this code is taken from the sample code that's been made available to you. And kind of like a pointer to the method where that code appears. So in this one, we're going to check the iCloud status. We want to find out if our iCloud user is signed in.
This can be done very quickly. And so this method is a new piece of API available to you through NS File Manager. It's the Ubiquity Identity Token. And you can call it right on the main thread. It's designed to give you a result very quickly. And you'll get back an opaque value that you can compare between application launches by archiving it.
or within the application, you can check to see, is it equal to the value you had before? Maybe if your user suspended the app, switched iCloud's accounts and went back, it may have changed. And if that value comes back as nil, then you know your user is not signed into iCloud.
So the next step is we want to add our persistent stores. And like I was saying before, we want to do that on a background queue. And so I wanted to show you, these are the dispatch APIs you can use, and they're, again, available in the sample code, to kick off a background queue to asynchronously load the persistent stores.
Now, there's a number of pieces of work that are happening in loading the asynchronous loading persistent stores. We deal with some other cases, and Nick's going to detail some of those for you later. But right now, I just wanted to focus on the steps involved in loading the iCloud store. And this walks you through the main APIs that interact with iCloud here.
First, now we want to use the file manager API to get the URL to access the iCloud container. And the reason why we want to execute this on the asynchronous call, on the background queue, is because if this is the first time your user has accessed iCloud through your application, or accessed iCloud through any application, there might be some file system setup that needs to happen, and it might take longer than you want your user to wait on the main thread.
So first thing we do is we get that URL. The next step is we want to get the iCloud store URL. And this is actually a method that we've provided and implemented in the sample code. And what this will do is we decided to follow the code path of providing access to the persistent store file when the user signed out.
So we're storing the persistent store file in the app sandbox. And so what this method is doing is it's creating a path that's uniquely constructed based on the iCloud user who is signed in currently. So that's the way we make sure that one iCloud account is associated with one store file with iCloud enabled. And you can look that up.
The next steps are we then want to set the options to enable iCloud for our persistent store file. And those are the persistent store, ubiquitous content name key. And then we want to set the options to enable iCloud for our persistent store file. And those are the persistent store, ubiquitous content name key, because iCloud is a very powerful tool for iCloud.
And then we want to set the options to enable iCloud for our persistent store file. And those are the persistent store, ubiquitous content name key, because iCloud is a very powerful tool for iCloud. And then we want to set the options to enable iCloud for our persistent store file.
And then we want to set the options to enable iCloud for our persistent store file. And because we're only working with one iCloud enabled store at a time, we just name this the iCloud store. That is what's identifying the name of the ubiquitous content that we're putting into iCloud.
Then the next piece is the persistent store ubiquitous content URL key. And what this does is this provides Core Data the path that it should use to store those transaction logs or the incremental changes that we're putting into iCloud. And the way we've constructed the iCloud data URL is by taking that ubiquity URL and tacking on iCloud data on top of it.
Then we pass those options to our call to add persistent store, and you can see we're using the Cloud Config here again. And away we go. Our coordinator should add the store. So the next step, and this is actually implemented in the asynchronous load persistent stores, is if we're successful at loading the iCloud store, then we want to check, do we need to seed any data into that iCloud store? So I wanted to call that out because you'll be able to check up on that in the sample code, and Nick will talk a little bit more about seeding your store. But this is the time in that asynchronous load persistent stores where you might want to do any other follow-up work based on adding that iCloud store. So again, you're not holding up your user interface while you're setting up the data.
And the last step here is we want our user interface to respond now that the new data is available. And so we do that by listening for a notification on our root view controller. And this uses a notification that Core Data generates for you, and that is the persistent store coordinator stores did change notification.
And that's an indication that there's been a change to the stores, and it's a good time to refresh because there might be more data available. And at that point, we're able to present our full UI, our application is launched, and our user is able to work with their full data set. And with that, I'm going to turn it over to Nick Gillette to talk about how to implement to your application lifecycle.
I'm Nick Gillette, I'm a software engineer on the Core Data team, and I'm going to talk to you today about the application lifecycle, which is really all the events that your application will encounter and have to deal with when it's using iCloud and Core Data. We'll talk about things like seeding, integrating changes when they come in from over the wire, responding to user events, and we'll also go over a few performance and debugging tips. First, I'd like to start, though, by talking about our sample application that we're going to ship.
So as Adam mentioned, this application is available to you today from the developer website. And I've created a single class here which encapsulates all of our Core Data and iCloud functionality that you can use in your applications if you desire. You should be aware that it's built with Arc, so if you're using your manual retain and release application, it's going to leak like crazy.
You can see that we have two targets here, well, two projects really. This is a shared workspace for this application, and it has a Mac OS X target and an iOS target that you can use to create an application that shares data between your Mac and your iOS device. The Core Data Controller is our central point of contact for all the Core Data related stuff. And the app delegate has been decreased in functionality from the standard Core Data template. It no longer has a persistent store coordinator, and its only responsibility is to initialize the Core Data Controller.
Inside the Core Data controller, you'll find all the necessary variables and things you'll need to work with iCloud, including the persistent store coordinator, a manage object context for working on the main thread, a few internal concurrency items, and an implementation of NS file presenter. You'll also find our three persistent stores that Adam spoke about, the iCloud store, the fallback store, and the local store that holds the 50 state data for our application. Our application is pretty simple in terms of the managed object model.
As Adam mentioned, we have a person and a state location. There's also a metadata entry for those of you that wish to sync some metadata with your application over iCloud. You can also see the two configurations that Adam spoke about. Now I'll go back to the slides and we'll talk about seeding.
So what is seeding? Seeding, as we think of it, is the process of injecting the data store with some initial data set. This usually means transferring data to the iCloud store from some local persistent store file. However, you can take it to mean whatever way you manage to inject the iCloud store with your initial data set.
Normally when you seed the data store, you'll already have an iCloud store in the coordinator, which you added with the persistent store options for Ubiquiti, the content name and the URL key. You can add another store to the coordinator by calling add persistent store and use the NS read only persistent store option to tell the manage object context which store you wish to insert entities into. This has the added benefit of also making sure you don't accidentally duplicate all your seed data.
Next, you'll need to actually migrate the objects into the store. And to do this, we must be very careful about memory pressure. So we can use a batch size on the fetch request to ensure that we don't keep too many objects in memory at once. When you migrate the actual objects, you can add them to the store, and then call NSManageObjectContextSave and reset for each batch size to free up the memory consumed by the inserted objects as well.
Finally, you want to mark the seed complete. And this is important so that no other devices try and reseed the iCloud store with your initial data set. After you've finished, you should clean up. In most cases, you'll be able to delete the seed store from the device, freeing up a little bit of disk space for your user. So let's walk through the seeding code.
So in the Core Data controller, we've broken out an area called application lifecycle that deals with seeding. And you can see in here that we've got two main methods. One is used for adding a person to the store, and the other is the main seeding method. This is called from a few different places in the application that I'll talk about in a minute.
Basically what we do in this application is we set up a separate persistent store coordinator for the seed. Now, you might be saying, wait a minute, you just told me that you could use the same persistent store coordinator. And this is true, except if you have a very large data set.
When you have a very large data set, you have to call manage object context reset to free up memory consumed by the inserted objects. Unfortunately, when you do this, any references to those objects that are contained in the fetched results array are going to be invalidated. And you'll no longer be able to use the results of that fetch request. This is inconvenient for the purposes of inserting data over a large set. So in the sample application, we create a separate persistent store coordinator which points to our seed store. It's still mounted read only so that we don't accidentally insert data.
And then we create a managed object context that we use specifically for fetching the seed objects. This is where the fetched results array will automatically do our memory for us. It will maintain one batch of objects in memory at a time, and then free out the rest of the objects.
We create a separate managed object context, which contains the iCloud store. And this managed object context points to our application's persistent store coordinator, where the iCloud store will be. We then iterate over every person in the seed store. And for each batch, we call managed object context save to write the changes to disk, and reset to free up the memory consumed by the objects.
Finally, if there are still changes left over, maybe we had a number of objects in the store that didn't correspond cleanly to a batch, we do one last save. But where does all this actually get called from? Adam mentioned a little bit of application logic that we had in load persistent stores.
And this method is responsible for loading all the persistent stores that are going to be used for our application. It takes responsibility for piping this call back to a background queue so that we don't impact our UI. In async load persistent stores, we go through and first load the local persistent store that contains the 50 states information. This should be very fast.
Next, if iCloud is available, we load the iCloud store, which may take a while, if it's the first time that you're adding it to the coordinator. For subsequent launches, this will also be very fast. And if necessary, we seed the iCloud store. Traditionally, your application should have a little bit more complicated check for this.
However, we wanted to make it as easy to use as possible in the sample app, and so we've included a simple pound-defined macro that you can turn on and off to see seeding happen. This is also a good way to profile it. After we've seeded, we dedupe, which I'll talk about in a little bit.
If iCloud isn't available, however, then we fall back to the fallback store. And this is that persistent store that Adam spoke about, which temporarily holds data that we want to put in iCloud. And this is a great way to give your user a seamless experience when they're using iCloud and when they're not.
This is something that we automatically detect in the sample application and switch between as needed. So if you sign out of the iCloud account and you're using the sample application, we'll automatically load the fallback store for you. As you can see, we also have some seeding logic in the fallback store, if you want to see that happen on a local device. So let's move on and talk about integrating changes.
In a traditional Core Data application, when you call NSManageObjectContext save, you can use NSManageObjectContext didSave notification to merge those changes with other contexts. With iCloud, we created NS Persistent Store did import ubiquitous content changes notification, and they had to make the slides bigger because of it. This is how we communicate to your application that things changed in the cloud. More importantly, it's how your application will respond and integrate those changes into its own managed object context.
But you need to be aware that unlike NSManageObjectContext did save notification, NSPersistentStore did import ubiquitous content changes notification, has managed object IDs, not NSManageObjects. So if you have any application level logic that does merging for you, you'll need to update it or at least make it aware that it's dealing with object IDs instead of managed objects.
Adam Swift Now let's talk a little bit about a scenario that some applications will encounter who wish to keep a consistent data set for their users. Let's say that I have an iMac that I've never used before. It's brand new, and I want to import all my contact records into this machine. So eventually, it creates a record for my mom. But as Adam ran into earlier, my iMac doesn't have an internet connection.
Maybe Comcast is having a bad day, or my router melted. And so I create the same record on my phone. My phone, with its persistent data connection, transfers that record into iCloud. Later, my Mac comes online and transfers its record. It also downloads the record from my phone. And likewise, my phone downloads the record from my iMac.
From our standpoint, you created two records in the database. From the user's standpoint, though, this doesn't quite look right. How do you deal with this? We call this unicking, and not in the traditional database sense of unicking, but in the sense that the user is expecting to see a consistent data set, unique by some value.
For our sample application, we've chosen to unique based on the email address. And if we were traditional database developers, we could use this query to find out all the duplicate email addresses in the database and how many times they occurred. How do we do this with Core Data? Well, we know that the table we want is a list of email addresses and the number of times they're in the database. And we can use NS Expression to get the count of email addresses in the database.
We can also create a fetch request that will return to us the email addresses and the count. You might be wondering how, since the count isn't a property on any managed object. Well, That's where the dictionary result type comes in. NSDictionary result type is a powerful construct in Core Data that allows us to create a simulation of arbitrary columns in the database. They're returned to us as key value pairs.
So when we execute this fetch request, we'll see this SQL come out in the console. If you'll notice, it looks remarkably similar to our original query. And this is what we'll see if we print out the results in the debugger. You'll notice that it's an array, just like a normal fetch request would return. However, instead of manage objects, there's a dictionary with account and an email address in each entry.
Finally, you'll have to fetch out all the duplicate objects. To do this, you can use an in-predicate and gather the array of values from the previous result. You can sort by email address to make unicking easy, and then execute the fetch request. Finally, the most important step, you'll have to elect a winning record. Now, you might be thinking this is actually the easy step, but only if you get it right.
The problem you can run into is that, let's say that you unique the records based on, say, who inserted them. So the iMac will always say, well, I want to keep my record, and it will delete the one from the iPhone. And the iPhone will say, I want to keep my record, and it will delete the one from the iMac. The net result of that conflicting merge is that there's no data left in the store, which is definitely not what your user expected. Adam Swift We recommend that you use a record UUID, such as NSUUID, to create a string UUID or a timestamp.
If your application requires it, you can also do multi-field unicking by hashing a number of fields into an integer constant that you store on your entity. Finally, you should use a batch fetch request for this. Even if your data set is small, it's possible that there could be a lot of unique data. You can never really be quite certain with any of your peers whether or not they've created duplicates that will be transferred to other devices.
So let's talk about the Uniquing code. So here in the sample application, we've created a section specifically for Uniquing, and it has one method called dedupe. Ddupe is called in the application whenever a seed happens. We don't call it on the merge. We don't call it when we traditionally merge changes from iCloud because we assume that we haven't created any duplicates, which knowing users is not necessarily a safe assumption. So in your code, you may want to call it after every merge.
The first thing that we do is we create a special managed object context to be used by this operation. This is so that we don't burden any of the other contexts, such as the main thread context, with this operation. And then we create our fetch request with an appropriate batch size. For us, it's 1,000 in the application. However, depending on the size of the entities in your data model, this may vary.
Next, we create our count expression. And one thing I left off the slides is the NSExpression description instance that we need to tell Core Data a little bit of information about the count's function. We give it a name, which is the key that will be referenced in the dictionary that's returned. We give it the expression. And we tell it what type of data it can expect as a result.
Next, we fetch the attribute description for the email address and use that on properties to fetch. For properties to group by, we simply set the email attribute. And then, of course, the dictionary result type, one of the most important parts. After we fetch the count dictionaries, we can enumerate them, and for any dictionary with a value greater than one, we pull the email address out and add it to an array. That array then becomes the target value of our in predicate, which is used with an email address sort descriptor to get out the array of all the possible duplicates.
And finally, we have a simple merge algorithm, which just sorts the UUIDs and elects one of them. Now, of course, your application may have something that's more complicated than this, but for us, a UUID was sufficient. Finally, there's the usual saving and manage object context cleanup for every batch. So that's Uniquing.
User events. So what do I mean by user events? Well, throughout your application's life cycle, there are various events that it can encounter that are initiated by the user. We focused on the deletion from documents and data where the user has chosen to prune your application's data from their iCloud account.
They'll receive a pop-up asking them to do this anytime they run out of quota space in iCloud, and they can either buy more or they can go through and wipe out your application's data. When this happens, All of the files that exist in the container will be deleted and become inaccessible to your application.
So what can you do about this? Well, there's no existing API today to help you with this problem. So we recommend that you use an NS file presenter to point to a file inside the container. You can keep it in a no sync or in a sync directory, depending on how you expect to deal with it.
If you're keeping the store file inside the container, this is a great thing to have as your presented item URL. The reason for this is that iCloud will come through and prune all the files in the container, notifying you that the store file is in the container. file has been deleted.
This is very similar to what happens when the account changes. Luckily, NS File Manager has some API to help you with this situation. There's the Ubiquity Identity Token method that Adam mentioned earlier, which you can use in application did become active, or wherever else in your application you feel it may serve you best, to detect that the iCloud account is not the same as the one that you had before. There's also a new notification called NS Ubiquity Identity Did Change Notification that you can subscribe to and you'll receive when the device's account changes.
Responding to the iCloud account change is fairly simple. For our sample application, all we need to do is remove the iCloud store from the persistent store coordinator and call load persistent stores again. However, this would be a great place to add the store to your coordinator as a read-only store if you wish your user to be able to still see the data that's in it.
So let's walk through that code. So here we have a method called iCloud available, which simply checks to see if we've stored a Ubiquiti token. And application resumed, which is where we actually check to see if the token changed. If it did, we call iCloud account changed. Now, because this is the same method that we use when we respond to the notification, it will take a notification argument, but we usually pass nil when we detect the change manually.
And there we call drop stores. And drop stores is pretty simple. It just removes whichever store we have mounted from our coordinator, whether it be the iCloud store or the fallback store. The reason for this is that we'll reexamine what we need to do with those two stores in load persistent stores.
Next, we call load persistent stores, and we fall back into that loop where we check what we need to do with the various stores on the system, whether or not we can use iCloud, whether or not we've had the fallback store before and need to seed it into the iCloud store, or if we need to seed the fallback store with some initial data set.
From the application standpoint, you'll notice that the app delegate has very little to do when the actual account changes. In Did Become Active, we simply just call application resumed and have the controller check for us to see whether or not any action needs to be taken. Finally, I'd like to talk a little bit about performance and debugging. One thing you should always remember when working with iCloud is that every time you call NSManageObjectContextSave, you'll actually generate two pieces of I/O.
One is the right to the persistent store, and the other is a log that goes out to iCloud. So you should be careful to coalesce changes as appropriate and avoid storing things like raw sensor data that can come out at a high frequency. For example, your application could receive Core Location updates at a rate of 60 hertz. If the location hasn't changed, there's little value to writing this data into the store. And as such, you may find that your user may not benefit from this data being synced over iCloud.
You also need to be very aware of memory pressure. Because of the integration with iCloud, your application will consume slightly more memory than normal due to us merging changes in in the background. And this is important because your user will expect the application to remain responsive and usable while we're doing this work for them. So you should be careful to use batched fetch requests whenever possible, and call NSManageObjectContextSave or reset as aggressively as you can to free up unneeded memory.
Finally, when things don't quite work out as you expected, remember that Macs are greedy peers. They will try and download as much information about your iCloud account as they can, as long as you've run at least one iCloud-enabled application. On Mac OS X Mountain Lion, you can use TextEdit, which ships with the system.
When it comes time to remove data, the best way to do this is with a coordinated write to delete every file inside the container. This will take a little bit of time to sync to all your different devices if you have a lot of files, so you should be patient and make sure that all the files you expect to be deleted have been before you begin working with the container again.
Finally, help us out and file great bugs. I've been reading a lot of your bug reports over the last six months, and I can tell you that there's a few things that make it a lot easier for us to tell what's going on. First, if you can, please include a sample application that demonstrates the issue you're seeing. If not, a verbose description of how you get the problem to reproduce helps us out tremendously.
If you can include the container, we would love for you to do so. This gives us an accurate record of everything that's happened with our integration and your account over the history of your devices. However, you should be aware that we can't guarantee confidentiality with this information. So if it's sensitive, you should prune it or generate the problem with a sample application and attach that container.
Finally, the most important thing you can do for us is attach our debug console logs. You can pass this argument, com.apple.cordata.ubiquity.loglevel, to your application as a launch argument or set it as a user default with the appropriate number. We like level three logs, but I have to admit they are very verbose and will cause a lot of console spew, which is great for me when I'm trying to figure out what's gone wrong, but might be a little hard to sift through all of them. Thanks for watching. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. on your own.
So now I'd like to take you through a few of the debugging functions we've included in the sample application. So we've included a couple of simple debugging functions that you can use with your application. One is to delete the container. And this simply goes over to the Core Data controller and calls nuke and pave.
appropriately named. The second is to copy the contents of the container to your application's local sandbox. Now, this isn't that important on a Mac device because you have access to the entire disk. However, on an iOS device, this will copy all the contents from your application's iCloud container into the sandbox where you can download them through Xcode.
And that is the best way to attach them to us in a bug. But what do these methods do? Under debugging helpers, see Nuke and Pave, and async Nuke and Pave, which is where the work is really done. The first thing we do is drop all the persistent stores. We don't want to accidentally delete the store file out from underneath ourselves.
Next, we get a file in instance of NS file coordinator and iterate over all the sub paths in the Ubiquiti container. We then do a coordinated write and tell it that we are going to be deleting these items, and then use the file manager remove item at URL API to remove the file. Now, as I mentioned before, if there are a large number of files in the container, it may take a little while for iCloud to transfer all of these changes to your various devices.
In Copy Container to Sandbox, we don't even bother with file coordinator, which has two advantages. One is that it won't trigger a download of the file that's a fault in your container. Now, this may be disadvantageous for you as an application developer, but for us, when we're trying to debug your container, it's critical for us to know what files are faults and have not been downloaded to the device. The other advantage is that because it doesn't trigger downloads, it doesn't block, so it's very fast, meaning that there's no risk of you getting stuck at Xcode for two hours waiting for files to come down from iCloud. And that's debugging.
There's a little bit of extra information we thought you should know. First is that please contact Michael Jurowicz if you have any issues with our integration. He's our developer evangelist. His email address is [email protected]. And there's a feedback list that you can email us at. We monitor it every so often, but we're busy trying to fix your bugs, so we might not get back to you right away. There's also some documentation you can look up on developer.apple.com. With iOS 6, we released some new release notes for iCloud that should help you along developing your apps.
Finally, you can find us on the developer forums. We do lurk there every once in a while and answer questions that we find relevant, usually with "File a bug." There are some related sessions. There's the iCloud Storage Overview, Core Data Best Practices, which was this morning, and Advanced iCloud Document Storage, which is tomorrow at 3:15.