2011 • 37:55
iCloud Storage enables your apps to keep user documents and data in iCloud, so your users can access the same content from all their computers and iOS devices. Gain a practical understanding of how iCloud Storage works and how to take advantage of it in your app. Learn how to use the Key-Value Store and see how UIDocument works with iCloud to store your app’s documents.
Speaker: Michael Jurewitz
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Hello and welcome to Adopting iCloud Storage Part 1. I'm Michael Jurewitz, the evangelist responsible for iCloud. iCloud Storage is a powerful set of APIs for storing user documents and data in iCloud. By using iCloud, you can make sure that your users' data is available on all of their devices, providing a seamless user experience.
Today, we're going to start by taking a look at the fundamentals of working with iCloud, so you have the information that you need to get started. Next, I'll show you how you can adopt the Key-Value Store and UI Document in your application to start storing settings and user documents in iCloud. So let's get started.
Now today, I want to make sure that you have the information you need to adopt iCloud in your application. So to do that, we're going to break things up into three different parts. First, we're going to take a look at the fundamentals of working with iCloud. Next, we'll talk about project setup and how to actually set up your Xcode project to work with iCloud. And finally, we'll look specifically at adopting iCloud. So let's get started first with the fundamentals.
Now today, all of us have many, many devices, whether it's an iPhone in our pocket, an iPad along with us, or even our Mac as well. And we all have data that we really wish would just be available on all of those devices, without us having to think about if we had copied that data to and fro. And that's exactly what iCloud Storage is designed to help accomplish. iCloud Storage is a new API available for App Store apps. It allows your application to upload files and data, upload simple Key-Value data, and it works continuously and in real time.
So let's look at some fundamentals here. With your application, anytime you're working with iCloud, you're going to have something called a Ubiquity container. And this Ubiquity container is where you store the files that you want to be able to move to iCloud. Now anytime you store a file in this Ubiquity container, it automatically will be sent up to iCloud. And anytime any other changes come in from any other device, iCloud will see that updated file, and it will make sure that your local application also receives that file.
Now, how do we actually handle sending these files up to iCloud? Well, for any document, what we actually do is we break it into discrete chunks. And these discrete chunks are what gets sent up to iCloud and becomes the representation of your document. Now, any time that you make changes to this document, we pay attention to just the pieces that have changed. And so we only send up the differences to iCloud. And this basically becomes version 2 of your document. So this is very, very efficient. It keeps us from having to send megabytes or gigabytes of data repeatedly.
But files can be large, and files also have a lot of associated information about them, things like their name, their modification date, their size, their file type. And so one of the things that we do is we actually aggressively push all of this metadata to iCloud, so that even before your file is finished uploading, you can already know things like the name and the modification date, the file type, etc. from iCloud.
Now, between OS X and iOS, there are some differences worth paying attention to. First of all, OS X is something that we call a "greedy peer." It makes sure that any files that you store in iCloud are aggressively downloaded to that Mac. On iOS, the situation is a little bit different. Typically, an iOS device is more space-constrained, and so even though your application knows all of the files that are available in iCloud, your application will need to individually ask for specific files to be brought down to the device.
Now there's some interesting things that we can do when you've got a document on, say, your iPhone and you happen to have your iPad, say, in the same room and on the same Wi-Fi network. Instead of taking that file and sending it up to iCloud and then waiting for it to download back down to your iPad, we can notice this situation and we'll simply copy this file directly between these two devices.
Now, as your application is working with iCloud, I mentioned you've got this thing called a Ubiquiti container. And this Ubiquiti container, again, is where all of the files that you want to move to iCloud are going to be stored. Now, fundamentally, your application needs some way of watching this Ubiquiti container and knowing when changes happen to it, say new files showing up.
And that's exactly what NS Metadata Query is designed to help you do. So, NS Metadata Query will help you watch that Ubiquiti container. And any time there are changes, say like new files showing up, the Metadata Query will alert your application and give you a chance to respond to that. Maybe you need to update your user interface to show that to the user.
So that's iCloud Storage. It really is primarily about moving data between devices. It notifies your application of file changes anytime you've got new content that's come in from the server. All of this works even if your application isn't running. So if the user hasn't launched your application, or if they hit the home button really quickly and put your application in the background, all of this still works.
It's all handled by the OS for you. Michael Jurewitz And the real goal here in all of this is to create a seamless experience for the user. You want them to be able to pick up any of their devices and simply have the data and settings from your application available to them.
So across the next two presentations, we're going to split things up into four main units. First, we're going to take a look at the Key-Value Store. Next, we're going to talk about working with UI Document. We'll also take a look at working with Core Data. You can actually use Core Data with iCloud now as well. And we'll take a look at working with individual files and data. But for the rest of this talk, we're going to focus on the Key-Value Store and UI Document. All right, so that's fundamentals. Now, let's take a look at project setup.
In getting your projects set up to use iCloud, there are three main things you're going to be working with: provisioning profiles, entitlements, and Xcode. So let's talk about provisioning profiles. Now, fundamentally, a provisioning profile identifies your development team. It tells the system, it tells us who you are. It also grants specific app IDs the ability to use iCloud. Now, provisioning profiles also do what they've always done.
They define the devices that your app can run on, and they also define your iCloud Storage location. All of the storage space that your applications use is going to be scoped by the team that you happen to be on. And we'll talk more about that in a moment.
Now, when it comes to entitlements, entitlements are an important piece of the iOS security infrastructure. And they are going to be what lets your application access its iCloud container at runtime. So basically what lets it access that Ubiquiti container. But these entitlements also do one extra thing. They allow you to specify the slice of your total iCloud Storage that you want your particular app to be able to use.
Now, when you want to get set up, you do all of this through the iOS Provisioning Portal. So, you want to make sure that you go to the iOS Provisioning Portal, that you go to the App IDs tab. Once there, you simply want to make sure that you scroll on down the page, and for the App ID that you want to enable for iCloud, just click the Configure button.
Once you're configuring this app ID, scroll down and make sure that you check "Enable for iCloud." That's all you have to do. Once you're done, simply hit the "Done" button. Then you want to make sure that you regenerate an existing provisioning profile that uses that app ID, or possibly go and create a new provisioning profile that uses that app ID. Once you've done that, simply click "Submit." And then you can either download that provisioning profile from the website, or within Xcode from the organizer, simply click "Refresh" and have Xcode download that profile for you automatically.
Now, once in Xcode, there's one more build setting that you need to check to start using iCloud. So first, select your project on the left, then select your specific application target and make sure that you're on the Summary pane. If you scroll all the way to the bottom, you'll find a build setting called "Enable Entitlements." When you check "Enable Entitlements," Xcode is going to automatically populate a bunch of information into your application for you. Let's take a look at what this is.
So you see that there are two main entries here. There's the iCloud Key-Value Store and the iCloud Containers. There's also this entry for an entitlements file. Now what Xcode has actually done is created an entitlements file for you. And these default values that you see for the Key-Value Store and iCloud Containers are simply based off your bundle identifier. In most cases, you can just keep this exactly the way it is.
Now in Xcode, you'll actually notice that there is this entitlements file. And if you take a close look at it, you'll see it's just an ordinary property list. In fact, you can see the entries for the Key-Value Store and the iCloud Containers. Here, they're called com.apple.developer.ubiquity_container_ identifiers and com.apple.developer.ubiquity_kv_store_ identifier.
Now for each of these, you'll notice that they've got this variable on the front, this team identifier prefix. What this does is basically looks inside the provisioning profile that you're using to sign your application, pulls out the unique identifier for your team, and sticks that at the front of these settings. And in this way, this is how we basically scope your team's storage in iCloud.
So at runtime, you'll find that your Ubiquiti container looks something like this. You've got your team identifier on the front, followed by typically your bundle ID. In this case, com.examplecorp.myicloudapp. And again, this ID is going to be what defines where this app can store its iCloud documents. Michael Jurewitz Now later, if I develop a second app, maybe an app called BookNotes that also uses iCloud, its Ubiquiti container identifier is going to be different. In this case, it's going to end in BookNotes. So it's going to have its own unique place to store its iCloud documents. Michael Jurewitz Now you can actually do something really cool. If you recall, the iCloud container setting took an array of Ubiquiti identifiers.
Michael Jurewitz So you could add your second app��s Ubiquiti identifier to your iCloud Storage. Michael Jurewitz This has the effect of basically extending your Ubiquiti container for your first app, so it can access all of the files and data from that second application. Michael Jurewitz If you've got a suite of apps that you all want to be able to work together and share this information, this is a great way to do that.
All right, so that's all you have to do to get started with iCloud in your Xcode project. Simply go to the iOS Provisioning Portal, enable the application that you want to use iCloud, download that provisioning profile, and check one setting in Xcode. All right, so that's project setup. Now let's take a look at adoption. And here, we're going to start by first talking about the Key-Value Store.
[Transcript missing]
Well, today on iOS 5, both stocks and weather take advantage of the Key-Value Store. Now, both of these apps are great examples for the kinds of use cases you should be thinking about. In the case of stocks, we're simply storing the ticker symbols the user cares about. In the case of weather, we're just storing the cities that they care about.
And if you really think about it, this is the model of that infrequently changing data that's perfect for the Key-Value Store. So let's take a look at the Key-Value Store API. Now in iOS 5, you have a new class to work with called the NSUbiquitousKeyValueStore. And anytime you want to work with this store, you first need to make sure that you ask the NSUbiquitousKeyValueStore for its default store.
As your application is first launching, you also want to make sure that you subscribe to the NS Ubiquitous Key-Value Store "Did Change Externally" notification. This is going to be what lets you know any time there's a change that's come down from the server that you should pay attention to.
As part of application launch, and only here, you also want to make sure that you call "Store Synchronize" after you've subscribed to this notification. This is going to be what basically reaches out to iCloud and makes sure that you have the most up-to-date information available to your application.
So let's take a look at storing data into the Key-Value Store. Now the first thing you need to do is ask for the default store, just like you always do. Next, you can simply use something like "set string for key." You'll notice this looks a lot like the same API that you would use for something like NSUserDefaults. So we've got our recent player name, we're saving it for the key "mjRecentPlayerNameKey," and that's it. That's going to handle storing that string into the Key-Value Store, sending it to iCloud, and then propagating that information out to all of the user's devices.
When it comes to retrieving data, that's also really straightforward. Again, we ask for the default store, just like we always do. And then we simply call "string for key" and pass in the key that we used to previously store this item. This is going to reach into the Key-Value Store and grab the most up-to-date information that's available.
Now, I mentioned before that when your application launches, you want to make sure that you subscribe to the NS Ubiquitous Key-Value Store Did Change Externally notification. So, what should you actually do when this notification fires? So let's take a look at the UpdateKVStoreItems method. Now the first thing you want to do is take a look at the notification object that's passed into this method and get its user info dictionary. Now within this user info dictionary is going to be the reason for the change, basically the reason this notification was fired. You can find that out by asking for the object for key for the NSUbiquitousKeyValueStoreChangeReasonKey.
Now the only reason that you want to update your local state is if you've had a change come in from the server or a change come in as a result of an initial sync. And you can pick up each of these via the server change and the initial sync change keys.
Now assuming that either one of these was the reason for this notification being fired, you then have the ability to figure out exactly which keys changed. That information is also in the user info dictionary and you can look that up. via the NSUbiquitousKeyValueStoreChangedKeysKey. Now once you have this state, you simply want to make sure that you read in these new values and update any local application state that you might have.
So here are some tips for working with the Key-Value Store. First, it's important to note that there is no conflict resolution with the Key-Value Store. The last value that you store into the Key-Value Store on any device is going to be what wins. Now this means that you need to be a little bit smart about the values that you actually accept from iCloud.
If you find that some stale value has possibly come in from a different device, maybe you're a game and the user is on level 10, but you see a value of level 2 coming in from iCloud, well, you need to make sure that you don't roll back your user's progress in their game. Instead, you'll want to notice that those values are different, take the value that you think is correct, in this case the fact that they're on level 10, and just write that value back out to the Key-Value Store.
Now, you can save related data in a single transaction using SetDictionaryForKey. So what this lets you do is basically take all the keys and values that you want to save, wrap them in their own dictionary, and then you set that dictionary on the Key-Value Store. In this way, all of the new values are either going to make their way up to the server or they won't. Say, for example, if the user ran out of space.
Now it's important to note that preferences and settings should still be stored locally. You shouldn't be using the Key-Value Store as a replacement for something like NSUserDefaults. So you want to make sure that you're still writing things out to NSUserDefaults, but the preferences and settings that are useful to share between devices, you should also be saving to the Key-Value Store. So in this way, you should be thinking of the Key-Value Store really as a transport mechanism for these settings.
All right, so that was the Key-Value Store. Now, let's talk about UI Document. UIDoctument is all about document storage. It's an abstract model class for managing document data. As an abstract class, all this means is that you subclass it to handle your storage needs. UIDoctument naturally supports files as either flat data or as file wrappers.
UIDocument does a lot for you. It automatically saves data whenever it's appropriate and it guarantees the atomicity of these saves. UIDocument can also automatically track changes that are made to your document via integration with NSUndoManager. UIDocument also provides asynchronous reading and writing of your data on a background queue, so you don't need to worry about blocking the user.
UIDocument also provides coordinated reading and writing. This is something that we'll talk more about in Part 2. Essentially, UIDocument provides the seamless integration with iCloud. So we're going to look at the four main ways that you work with UIDoctument. We'll show you how to subclass UI document, how to locate your Ubiquiti container, how to start up your metadata query, and also how to handle the general document lifecycle.
When it comes to subclassing UI document, it's actually incredibly straightforward. There are only two methods that you need to implement: contents for type error and load from contents of type error. Basically, just how to write your data and how to read your data. That's it. So let's take a look at an example implementation of contents for type. So first of all, we simply want to prepare our data for archiving. So we create immutable data and also a keyed archiver.
Next, we simply want to archive the state that we care about. In this case, we're encoding a photo name as well as a photo description. And perhaps most importantly, we're also archiving away a document version. Now, if you've never done any work with document-based applications, I'm here today to tell you that at some point in the future, you are going to want to change the type of data that you're storing or the way that you're storing it. So actually saving off a document version, basically what version of your application wrote out this document, is a really useful way so that when you read it back in, you know what should and shouldn't be there.
Now finally, all you have to do is tell the archiver that you're finished encoding and return the data. And UIDoctument takes care of the rest. It handles writing that data out to disk, making sure that iCloud notices these changes, and that all that information makes its way up to iCloud.
Next, let's take a look at Load from Contents of Type. This is basically just the inverse of writing out your data. So all we need to do is create a keyed unarchiver and simply initialize it with the contents that are passed into this method. It's basically the data that we want to read.
Now you'll notice the first thing that we do is read out that document version. That way we know exactly which contents we should expect and which keys we should use to get those contents. So we read back out the photo name, the photo description. We save these into instance variables on this particular UI document. And that's it. That's all you had to do to read your document.
Now, locating your Ubiquiti container is a very important thing when you're working with iCloud, because this is where your iCloud documents are stored. Now, it's important to note that you will only be able to reach this container if the user is signed into iCloud. This is basically also the way that you have to know if iCloud is indeed available to you. The way that you determine that is through NS File Manager's URL for Ubiquiti Container Identifier. Let's take a look at how that's used.
You typically want to locate your Ubiquiti container as part of your application launching and resuming. So here we're going to take a look at some code as written in "Application did finish launching with options." The first thing you want to make sure you do is call NSFileManager URL for Ubiquiti Container Identifier. You'll notice we've passed in nil. When you pass in nil, the APIs will automatically look at your entitlements, pick out the topmost Ubiquiti container, and use that by default.
So, NS File Manager will go off, try to locate your Ubiquiti container, and it will turn back a value to you. This is going to be what lets you know if iCloud is or is not enabled. If you get back a valid URL, then you know that iCloud is there and you should continue with your iCloud-specific setup. If you get back nil, then you know that iCloud is not available and you want to continue storing your data locally.
Now, there's one small problem with this approach. URL for Ubiquiti Container Identifier can block. And I'm here to tell you, also as the performance evangelist, you shouldn't put any blocking code in "Application did finish launching with options." So, you're actually going to end up writing code that looks a little more like this.
In this case, we're taking advantage of Grand Central Dispatch to have our calls to NS File Manager done ASAP. So, let's take a look. So, the first thing we want to do is get a reference to the default global queue. And we do that simply by calling dispatch get global queue and passing in the dispatch queue priority default.
Next, we basically want to take this call to NS File Manager and just have it executed asynchronously. So we call dispatch async, pass in the global queue, and then also pass in a block representing the work that we want to be done. Now, that call is going to go off and be executed in a background thread.
And once that call completes, we want to do a dispatch async back to the main queue, in this case the main thread. And we simply want to update our own state with whatever the result of that call was. So here we call self, update with Ubiquiti container, and pass in the container.
Now, I mentioned before that NS Metadata Query is what you use to discover changes that happen to your Ubiquiti container, typically when, say, new files are coming in from iCloud. The important thing to remember is that this Ubiquiti container is connected, essentially, to all of a user��s different devices. And files may be created or even deleted on different devices.
And your app needs a way of automatically discovering these changes. And that��s exactly what the Metadata Query is designed to do. So, when setting up your Metadata Query, there��s a few things you need to do. First and foremost, you want to make sure that you allocant and init an instance of NS Metadata Query. You also want to make sure that you set up search scopes and a predicate for the query. In the case of search scopes, you��re basically telling the query, "Where do you want it to search?" within your Ubiquiti container.
For the predicate, you��re telling it what types of files would you like it to search for. In this case, we��re simply looking for files that happen to have a file name that ends in .txt. So, we��re just looking for text files. Now, there are also some notifications to subscribe to. So, let��s take a look at that.
So you want to make sure that you subscribe to the NS Metadata Query Did Finish Gathering notification and the NS Metadata Query Did Update notification. Once you've subscribed to both of those, you simply need to make sure that you start the query. Now, I mentioned before that we had these search scopes. Now, you've got two different search scopes that you can choose from. You can use the NS Metadata Query Ubiquitous Document Scope.
In this case, this is only going to be searching within the Ubiquiti Containers Documents folder. This is just a folder that you can create for yourself if you want. If you have things the user really does think of as actual documents, you would store them in this folder. You also have access to the NS Metadata Query Ubiquitous Data Scope. This tells the metadata query to search everywhere within your Ubiquiti container.
So let's take a look at the document lifecycle of actually working with UI document. Specifically, I'm going to show you how to create, edit, save and update your UI document. So let's take a look at creation. Now, creating a UI document is really straightforward. Here, we've simply got our own subclass called MJDocument, and we simply need to call Alec and then "init with file URL," and we pass in the location on disk where we want to eventually save this document.
Now, once you've called that, you just want to turn right back around and call "save to URL for save operation completion handler." Now, you'll notice what I'm passing into this particular method is "UIDocumentSaveForCreating." We are telling our document subclass that this is the very first time that we are saving this file.
Now you'll also notice that this method takes a completion handler. This gives you the ability to supply your own block to have your own code executed once the save has finished. And what gets passed into this is whether or not the save was successful, so you can do different things depending on how that ended up.
When it comes to editing your UI document, this is where your application adds its value. This is how the user is going to be interacting with their document. UI document can keep track of quite a bit, but it needs your help to track the state of your document.
For UI document to automatically track these changes to your document, you need to provide some help. In this case, you can do a couple of things. The first, if you actually have operations on your document that should be undoable, you can register those undo operations directly with the document's undo manager. UI document will use this information to notice any time there are changes and will automatically mark the document as dirty, and thus eligible for automatic saving.
Now, if you have any changes that shouldn't be undoable or don't really have a clean concept of being undoable, UIDocement has a method called UpdateChangeCount, and you can simply pass into this "UIDocumentChangeDone." This tells UIDocement that you made a change, it's not something that's undoable, but the document should be considered dirty so that possibly it can be automatically saved.
Speaking of save, save is done for you automatically. But let's say that for whatever reason you really want to be able to save a document at a particular point in time. Well, the good news is you can do that. And you simply do that by calling "save to URL for save operation completion handler"�� the exact same method we used initially to create the UI document. In this case, though, you pass in for the save operation "UI document save for overwriting"�� and this simply tells the necessary machinery that this document already exists, you're just providing a new version of it.
Now, your UI document needs to be able to handle updates that come in from the server. So the first thing that you need to do is make sure that whatever class is presenting your UI document to the user, that it has subscribed to the UI document state changed notification.
Now, when you subscribe to this notification, there's going to be a number of different states that you have to handle. Let's talk about a few of them. The first is UIDoctuement State Editing Disabled. Now, this state typically happens whenever you've got a change that's coming in from the server. For a very brief period of time, your application needs to stop the user from editing that document while the two versions of the documents are compared.
You also have UIDoctuement State In Conflict. This is what you're going to see whenever you have a change you've made to a local document combined with a change made to the same document on a different device. And, in the case that iCloud wasn't able to automatically rectify these differences. In this case, you're going to get a copy of both of these files on disk, and you have the opportunity to help the user pick which one they want to use.
You also have UI Document State Saving Error. This is something that you should only typically run into if, say, the user has run out of storage space in iCloud, in which case the OS will ask them if they would like to purchase more. There's also UI Document State Normal. This is what you should see a UI Document return to after you've been in an editing disabled or a state in conflict, once both of those states have basically run their course. This is the normal mode of operation for a UI Document.
So let's talk about some tips for working with UI Document. First and foremost, remember you need to make sure you call URL for Ubiquiti Container Identifier via Dispatch Async. It's very important you call that asynchronously. Now, when it comes to version conflicts, you want to make sure you handle these discreetly. Again, this is when you've had a new version of a document come down from iCloud while you already have existing changes for that same document.
What you don't want to do is pop up a big dialogue in the user's face and make them deal with the conflict right this second. Instead, you want to provide some discreet indication in the UI that that document needs their attention, but let them choose when they actually want to resolve that conflict.
Another thing that you should consider doing is uniquely identifying documents with a UUID. This is a universally unique identifier. So iCloud simply uses the name of the file on disk to try to tell the difference between two different documents. So if you create Untitled Document on one device and Untitled Document on a second device, these look like the same files to iCloud.
So you should consider embedding your own unique identifier into these documents. That way, when a version conflict arises between something like these two documents, you can actually load those documents programmatically, look at the unique identifier that you stored in them, and if you find that those identifiers are different, you know they're different documents, and you can resolve the conflict for the user automatically without actually having to bother them. And again, remember document versioning. At some point, you're going to want to change the data you store or how you store it, so make sure you save off what version of your application actually wrote that document file.
So, we looked at the fundamentals of working with iCloud, showed you how to set up your project to work with iCloud, and looked at how to actually adopt iCloud in your application. In Part 2 of this presentation, we're going to show you how to work with core data and individual files and data.
So you learned the fundamentals of working with iCloud, saw how you can use the Key-Value Store to store user settings, and learned how you can use UI Document in your application to store document-based data in iCloud. I look forward to seeing the great apps that you'll build that use iCloud Storage.