Essentials • iOS • 54:06
UIDocument provides a powerful way to adopt iCloud Storage on iOS. Learn how UIDocument can help your document-based app more easily adopt iCloud Storage and understand the key concepts for working with documents and storage in iCloud.
Speaker: Luke Hiesterman
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it may have transcription errors.
We're going to deal with this sort of big topic that is building document-based applications, and in particular, building document-based applications that are going to live in iCloud. Because if you're going to write a great document-based application on iOS, you're going to use iCloud. So it's a big topic, document-based applications, and in particular in iCloud. But we're going to try to hit on some key points that will help you write better document-based applications using UIDocument. So how we're going to do that is walk through actually building an app. We're going to walk through some of the key steps today in building a working iCloud application. And the main areas we're going to focus on in building that app are application design, things we can do up front to ensure that our application is well tailored for working in iCloud. We're going to talk about document design, which is the thinking that we need to put in to making sure we have a well-tailored format for writing our data to storage and syncing up to the cloud. And lastly, we'll talk about finishing touches, things to make sure we have taken care of in our application before we ship it to users. So I'd like to start out today by actually showing you the app that we're going to be working with. And this is version 2.0 of the Cloud Nodes app that some of you may have seen in last year's demo. Here's my application, Cloud Notes.
And it wants to know if I want to use iCloud. I, in fact, do. And this application presents me with a list of note documents. I have a couple. and each note document contains within it several note records. So a note document is a list of notes where each note has a has a picture associated with it, and some text. In this app, I can create new documents. I can make some stage notes for myself. You know, something like, you know, talk loudly. I have a microphone for that, thankfully. And, you know, enunciate.
And that's the kind of thing that this app does. I can go back, close it, save it, reopen it, my data's there. Pretty simple application, document-based. And we're going to spend the rest of the talk talking about the key pieces going into building an app like this. So that's what we can look forward to. First step in this is application design.
We want to determine what are the key steps that we want to take care of up front when we're first building our application to ensure that we're in the best place to work with iCloud. And the first step in this is deciding where are we going to store our documents. This is a very key question if we're going to be a document-based application. Where do our documents live? And our basic choices are files are going to live locally or they're going to live in the cloud. And the message that I really want you to take away from this talk is let your users make this decision. Documents are either stored locally or they're stored in the cloud. And we want the decision, when we ask the user to make it, to be as easy as it can be. So we want to ask them once, like the first time that they launch the application, and when they decide, it's completely all or none.
They say, yes, I want to use iCloud, then you're going to store all of their documents in the cloud from that point on, and not even ask them again. One of the things that this implies is we don't want to necessarily think about not only storing some in the cloud and some locally, but also let's not worry about trying to sync, have mirrored versions of our documents locally at the same time that we're putting in the cloud. It's a very simple user experience to understand. We're just going to put everything in the cloud or everything locally if that's what they choose and not try to maintain local versus cloud versions. If they sign out of iCloud, they won't be able to access their data anymore, but that's fine. They'll be able to sign back in and access their data if they so choose. They can understand that. So we want to have something that looks like this on the first time they launch their application. You don't need this exact UI, but this is the rough idea of asking your user, hey, do you want to use iCloud? They decide yes or no. You don't have to ask them again.
After you know what you're going to do, and we're going to assume that what you're going to do is use iCloud for the purposes of this talk, let's talk about how you write sort of the bootstrapping code in your application, the stuff that you do up front so that you're prepared to access iCloud storage. And many of you understand that as accessing URL for Ubiquiti container identifier.
So this is work we're going to do in application did finish launching. Right when your application gets started, we're going to call a new API that we introduced in iOS 6. And this is NSFileManager UbiquityIdentityToken. And the reason we introduced this API is because the URL for Ubiquity Container Identifier can take up to a few seconds to complete. Many of you have run into this problem. And it's not something that's safe to call on the main thread. and it doesn't guarantee a quick answer to the question of, is iCloud enabled? So this is a method you can call safely in your application did finish launching method that if it returns non-nil, that tells you iCloud is in fact enabled on this device. So you call this right away in application did finish launching to know if iCloud is enabled. After you've determined whether it is enabled, you register for another bit of new API in iOS 6, a notification, NSUbiquity identity token did change notification, that tells you whether the aforementioned token changed, or tells you when it changed, rather. So if you launch your application and the user signed in, but later they signed out, you'll get a notification that tells you that.
Or alternatively, if your application launches, but the user hasn't set up an iCloud account yet, if they sign in later in the running of your application, You'll get a notification so you know that they signed in, and then maybe you can deal with iCloud at that time. So let's go ahead and assume iCloud is enabled, the user has an account. Now we have to access URL for Ubiquiti container identifier. This is where we're going to get the actual location in storage where we store our data. And as I mentioned, since I can take up to a few seconds to launch, it's only safe to call this method on a background queue. We don't want to call it on the main thread, lest we potentially lock up the UI.
So what makes this sort of interesting is it might take up to a few seconds, but it also might be fast. We don't actually know. This is sort of undefined from the application's perspective whether it will be really fast or a little bit slow. And so we have to do a little extra work to accommodate for this sort of known unknown in that if it does take a long time, we want to put up some UI so the user knows what's going on because we're not going to allow interaction with our application until we have that cloud storage identifier.
And so what we do is we start some kind of timer. And we're looking to see how long it takes to return from this method. If it takes some non-trivial amount of time, say 150 milliseconds, we hit a timeout. And we say, OK, it's taking a non-trivial amount of time. We want to display some UI to the user so that they, in fact, know what's going on. The reason we have this timer is because if it does return quickly, we don't want to flash any UI on the screen.
That'll just be distracting. But if it, in fact, does return quickly, we don't need any of this. We're gone. Don't flash any UI to the user. And we can move on with life. We have our URL, and we can use it. So with that, I'd like to show you actual code that we will use in Cloud Notes to bootstrap our application.
So this is Cloud Notes 2.0. And you'll notice on the left side of the screen, we've got our source files. And I just want to give you a quick rundown to begin of what you see here. We have an application delegate. We're going to need that. We're going to handle application launching there. We have a note document. That's our UI document subclass handling our reading and writing of data. I wrote this extra little class here that I call Cloud Manager that, from an application design perspective, I did this so that I could just have sort of a pulled out abstraction of the non-UI related bits of interfacing with iCloud. This is actually where the code lives that goes and accesses the URL for Ubiquity container identifier. And then it will store that value within itself. It's a singleton class. So that it is globally accessible and any other pieces of my application will be able to use that object to determine the URL for Ubiquity container identifier because it will just store it. And other bits of my application can access it directly and synchronously. So I'll just show you the API actually that I've put here in my Cloud Manager. It has a property to tell me if the cloud is enabled. And the semantic of that is, one, I determined that iCloud is enabled on this device and the user selected to use iCloud. And if both of those are true, this will be yes on my little Singleton class. And then it has a property that tells me where the data directory is and the documents directory as derived from the the URL for Ubiquiti container identifier. And for those of you who maybe don't know, the distinction between a documents directory and data directory is documents is where user visible documents live, and data is where your application metadata lives.
And to make this sort of extra convenient for my application, this Cloud Manager will automatically make the data directory and the documents directory be the right thing, whether I'm using iCloud or using local storage. So it'll return a local URL if I'm using local storage. And it makes the architecture of the rest of my application easier.
And then I have a root view controller, which is the thing that shows my document list. So right now we want to look at what we do in application did finish launching. There's a few steps we're going to walk through. And actually, you know, before I run this, I'm going to go ahead and just run the application on my device.
And we'll see where we are before implementing this iCloud code. Launch Buddy, there we go. Okay, so in this state, I haven't implemented the iCloud stuff, so my application is just showing local notes. I can, even though I'm not using iCloud, I can add something local. So, you know, I can add a local file here in the local zone.
and then, oh, I forgot to set something up on this app, so that doesn't work. That's a setup problem with my phone, sorry about that. But, so I'm in just the local state, And I'm gonna go write some code to enable iCloud. Let me kill that. So let's go.
Ta-da, back to my machine. Good. And so let's walk through the steps of this. First thing I'm gonna do is check what the user's iCloud preference is. This is the idea for the very first time we launch our application, we wanna know whether the user is going to use iCloud. And it's also called setup if necessary 'cause we're gonna set up our iCloud storage if the user is in fact using iCloud and it's enabled. Okay, so I'm also going to register for a couple of notifications. And two of these notifications are ones that I've defined myself. They're container will begin fetching and container fetching did end.
These are actually defined by my cloud manager with the idea that this is how I set up that timer for the UI we talked about earlier. When the cloud manager starts fetching the ubiquitous container identifier URL, it sends a will begin fetching notification. My app delegate, which is responsible for the UI, will start a timer, and if it does not receive the fetching did end notification, by the time the timer times out, it will put up UI. If it has put up UI, it uses the did end notification to tear down that UI.
And let's look at the implementation of this sort of critical method, check user iCloud preference and set up. All right, great. So this is, as I said, the first thing we end up actually doing is asking for the Ubiquiti identity token. This is our new bit of API.
Then we're going to check our user defaults, because we're going to use NSUserDefaults to store the user's preference as to whether they use iCloud or not. And so I'm asking for a key that I've just called use iCloud storage. And I'm gonna skip over this little bit of code later 'cause we're gonna use it in a subsequent demo. But then I check if my, If my token is valid, which means the user has signed into iCloud.
then I look at their choice. And if they didn't have a choice, then this basically tells me this is the first run of my application, and I should present them a UI, in this case, my UI is a UIAlertView, to ask them if they should store documents in the cloud or on the device. And if that is not true, if I have a choice, and that choice is yes, then I can just tell my cloud manager to set iCloud enabled as yes, and that will be globally accessible for all of the other code in my application to be able to know that we are in fact using iCloud here. And so, okay, now that I've just written that little bit of code to set up iCloud, let's run my app again and see how we're doing. So now we've switched to iCloud. If this were my application's first run, like we saw in my earlier demo, it would pop up an alert that asks me if I want to use iCloud. And we see all of my notes there. So I can read them. It's downloading it from the cloud.
Data comes in. I get it. Sweet. And there -- that's all there is to it. I've bootstrapped my application for using the cloud. And any other bits of my code that need to use it will access the URL via the Cloud Manager singleton. and also know whether I'm signed in by asking the Cloud Manager Singleton. This is the part of our thinking where we figure out the best way to store our data. If we're a document-based application, our data, which is to say the user's data, is the heart of what our application is all about. And figuring out the best way to store that is really going to make our application as good as it can be.
So the things we're going to talk about are using file packages here, things to avoid storing in your document, how to have sort of forward-thinking design principles, and we're also going to talk about how to design a document that is able to have previews. As you saw when I first showed the application, my document had previews. That wasn't true in the last demo, but we'll get there. So, first of all, file packages. This is really the biggest thing about document design that you want to take away. If you're not using file packages today, then it's something I hope you will be thinking strongly about when you walk out of this talk.
And what file packages are is a directory on the file system that is treated as a single document to your user. And this has some advantages. At a very simple level, it makes it so that you don't have to take maybe disparate parts of the user's data and shove them into a single file blob. But more than that, it can make your application perform better because when you change just a single part of your document, if that single part is represented by one little file inside your larger file package, we only have to write that one little file out to the NAND storage. And likewise, we only have to upload that one file into the cloud. So we use less network traffic, and our saving action can be faster.
So let's talk about how we do that. If you have a, you know, if we started with a note document, and we think of, okay, we've got all this content in a note document, and maybe in our first iteration of writing our application, we just took all that content and we wrote it out into a file, and we have a file blob. We're thinking, you know, I think I can make this better by moving the file packages, but how do I do that?
Well, the first step is breaking things out into logical components. Since a note document has several notes associated with it, we can say, well, each note is sort of a distinct component. And we can take all this content and say, well, this is really note one, two, three, et cetera. And now we have, assuming we had five notes in a document, we went from a single file blob to a directory containing five files. And each of those files can be written independently and uploaded to the cloud independently.
We can actually go a step further. We can think about separating assets from content, which is to say, if you think about assets as things like images, movies, things that would be treated as attachments in your document, and they're separate maybe from the core content, like the text. In the case of Cloud Notes, each note itself has text associated with it, and an asset, which is the limits that you see, We can break those apart too. And we can say content goes over here, assets goes over here. And now we've moved to 10 files inside our file package instead of one big file.
And when the user edits, say, the text, we can just save out that file that has the text for that one note, and we save out one file out of ten, one little text file, and we don't have to spend the time writing out all of that content, nor do we have to spend the time syncing that content up to the cloud. Another thing you can do is try to separate out things that are edited less frequently.
This also applies to actually things that are edited quite frequently. If you have knowledge about your application that a particular part of the document is rarely edited or there's a particular part of the document that is edited all the time, those components are candidates for moving out into their own file. So if you have something that the user edits constantly and you can stick it in a nice separated component from everything else, then when the user makes those edits all the time, you're only just writing out that little component. So, help me.
Now let's think about the actual code for reading these file packages. If you've never actually done this before, maybe you'll find this helpful. So this is how you do this in a UIDocument world. We have our canonical reading method in UIDocument, which is load from contents of type. And when we're using file packages, that type, or rather the contents, will be a NSFileWrapper. That's what we give you when you're using a file package. And the way to put this into your model is to have sort of a master file wrapper that you keep a reference to. It can be like an instance variable in your document.
And you just take the contents, which is a file wrapper that we've given you, and assign that to your instance variable. So now your model, which is a master file wrapper, is the contents that we've given you. You can also use an index file that is part of your document file wrapper that helps you understand what all everything else is in this document. In the case of Cloud Notes, I've got some arbitrary number of notes inside a note document. And I use an index, so that index will tell me how many notes are in there and what their file names are, how I can access them. So you can peer inside the index to know what you actually have in your file package. And right away when you load your contents, you'll just kind of load your little index into your model. So there you've got your model. When it comes time to write out your model, the way that that works in UI document is we always want you to snapshot your model and return to us a snapshot that we can write to disk.
So when you're using this sort of master file wrapper as your model, then snapshotting it is really just a matter of creating another NSFileWrapper instance with the contents of the sub file wrappers of your master file wrapper. So that will act as a snapshot of your data that we can take with us and write to storage.
This is actually where the NSFileWrapper smarts kick in and optimize the behavior for you because NSFileWrapper is smart enough to notice subfile wrappers that haven't changed, and it won't bother writing those out to storage. So only the stuff that you've changed will actually get written out at this point, and that's what makes it fast. In between reading and writing, how you manage your model is as changes come in, you make those changes directly to that master file wrapper that we've saved off, and that's what we're treating as our model. So if I add a new note... I create new file wrappers, add them inside my master file wrapper, and I update that index file. And then I move on with life. And part of the way that UIDocument is designed is that we only save when we're asked to.
So we will update that file wrapper that we're containing as our master file wrapper and then wait for contents for type to get called, which will be invoked when autosaving happens or when the user closes the document. So that's how we use the file package. Let's talk about how we can think ahead and design our document today so that we're in the best position to push updates to our application out to our users in the future.
And really what this is going to be all about is thinking about the fact that when you write version 2.0 of your application, you may also write version 2.0 of your file format. If you decide that there's extra data that you want to store in the document, or you want to store your existing data in a different way, You want a way that your previous version of your application can deal with this in a graceful manner. And the way that we do this is version your file format. This is something that we want you to write into your document format today so that you have a field, and if you call your document format version 1.0, you have that field that you can mark up to 2.0 in a future update in your application.
And this is especially important in today's world of multiple devices because it's likely that you're going to run into a situation where the user doesn't update your application simultaneously on all their devices. So you can very easily run into a situation where the user is actually using different versions of your application on different devices. And all of those versions need to be able to coexist as gracefully as possible. So that's what file format versioning will help you with. And you can... make your versioning help you as much as possible by defining levels of interversion compatibility. And what that means is maybe in an ideal world, you make a change to the file format in version 2.0 of your application and your document format. But you do so in a way that version 1.0 of your application actually is able to safely read and write that document. So you can actually, in addition to the format version, have a field also contained in your document format that tells previous versions what they're able to do with that.
And it might say, hey, this is actually read/write compatible with version 1.0 of my application. And one of the ways you can get to that place is by designing version 1.0 of your application to ignore fields that it doesn't understand. So if it sees a field in your document that you didn't have code for in 1.0, the version 1.0 of your application should probably just not touch that field. That will give you the best chance to be able to write updates in the future that are able to write out new fields and version 1.0 of your application can actually read and write that document without screwing things up. So that's how you can get there. In some cases, that won't be possible. You'll make changes that that the old version of your application won't be able to safely write out.
But you might still be in a place where it can safely read version 2.0 of the document format. That's another marking you can make in the file format itself. You can say this is version 2.0, but version 1.0 of my application can safely read this file. And so version 1.0 can then do the right thing in terms of being able to read it, display it to the user, but it won't let the user make changes to that document. And, of course, in the worst-case scenario, we make an update to our document format that is simply not compatible between versions.
And then we mark that in the file format itself. So version 1.0 of our application comes along, reads a file saved by 2.0, says, oh, this is 2.0 version of the file format. I don't understand this. And more than that, I was told by the info I find in this document that I shouldn't try to read this. So putting that information, putting those fields in your document format today will help you tomorrow. Let's talk about things not to put in your document, since we've just talked about great things to put in your document.
One is maybe not so obvious. Try not to save scroll positions in your document format. And what I mean by that is, like, U.S. scroll view content offset. That's something we want to avoid saving in our document. There's a lot of reasons for this. One is very simply, like, between different versions of your application, different versions of the iOS software, and different devices, perhaps, then the scroll view position can change. And you wouldn't want to be stuck in a situation where the user is opening a document and you're writing data out just because they opened the document. That's something we want to avoid. And so to sort of go along with that, we also wouldn't want to save the timestamp for the last time the user opened the document.
One bad situation that this particular practice can get us into is imagine that we have one instance of our application running with the document open. And we take another instance of our application, open that same document. And our new instance says, hey, I've just opened the application, or I've opened the document. I'm going to write out a timestamp for this. And this already running instance of our application sees an update because it comes across the cloud to this document that we wrote out the timestamp. So it reopens the document.
And because it did that, then it rewrites a last open timestamp. And then that goes to the cloud back to the other device. And now they start fighting for who gets to write the last open timestamp the most. And that's going to chew up network bandwidth, chew up CPU, chew up everything. That's a bad situation. So the moral of that story is avoid anything that can result in a sink loop.
Last thing we want to think about here is if you have any sensitive data that can be stored in your document, think about this if your application has a means of publishing the document. This is pretty common that we have a way to put a document on the web, email it to somebody just with a couple of taps. And if your application has functionality like this, and yet you have information in there like an undo stack or user information, any kind of user information that people wouldn't necessarily want to publish along with their document, then it's a good idea to think about having a step that strips out this kind of information.
So one feature that people ask how to do a lot is having a document preview that they can access on their device so that they can have something like a document chooser that shows previews a la iWork without having to download all the documents because the basic story in iCloud is you have to download a document before you get to access any part of it. So, we're gonna talk about now how to write that. So, in the beginning, there was your documents, and they were in the documents directory.
What we want to do to create a preview is move over to the data directory, which is where we put application metadata, and create preview files over here. And we want to establish a one-to-one relationship between preview files and documents, and that relationship is mapped to by the document name.
That way, just by discovering the document, then we can also know the URL where its preview file is. And once we have this, we know our file list from an NSMetadata query, and then we can access the URL for the preview file and download just that, rather than downloading the entire document.
You might have a 200 megabyte document file, but you've got a nice thumbnail image that you write out as a preview image that's only a few K, and you can just download that and display it to the user in some kind of document chooser and not download the whole document unless and until they choose to open it.
So let's look at actual code that will do that for you. So we're going to look in the UI document low-level writing method, write contents and attributes safely to URL. This is a method on UIDocument that you can override that is called in a background queue, UIDocument's managed file access queue.
so you're safely in the background. And in here, we want to, using that method, write out a preview using the preview URL that we've established and write it to that URL. Now, there's one thing I had an updated version of this slide, which I guess I didn't include in here. So take note of what I'm about to say because it's actually important. The part that is highlighted right now should be in NS file coordination. So you want to create an NS file coordinator instance and do coordinate for writing. You want to coordinate the write out of that preview data as you would anything that's in iCloud storage. So we're working on pushing out to you actually the code for this CloudNodes app that I'm showing to you. And in that code, this is done right. So take note of that. So I'll show you that now. All right.
So, if we go over to our note document, and this is where we're going to write out the content, I have this little method up here that returns me a little NSData preview JPEG that I'll be able to use for my preview data that I'll write out to disk. And then the code here is essentially what we just saw on the slide. After I have called super, which will write the actual contents of my document out, if that has succeeded, I will go ahead and take the -- I create a URL. In my case, I simply append.preview to the end of the document name, and I do store it in the data directory rather than the document directory. But so if I have note1.note -- that's how my -- I use the.note extension for my files -- then the preview file is called note1.note.preview. And I put that in the data directory. And then I actually properly create an NS file coordinator and coordinate writing to write the preview file out. And that's just taking the data and writing it to the proper URL. The other bit I want to show you is how to handle this on the other end, the receiving device. And that's going to be in my root view controller, which has a table view that shows all my files. That's my version of a document chooser. I have this very simple self-wrote and the next path.
And I'm going to put the preview image in the cell's image view, but I start out just by setting it to nil. And then I'm going to call this method, which is load preview for index path. And this guy is a little asynchronous operation that goes and fetches the preview image and will apply it to the cell.
So I'm going to do that. And then let's go look at that code. In here, I go look at my model, which is this little file representation. And the file representation will tell me the document URL for each row and also the preview URL associated with it, which I know because I know to take note1.note to note1.note.preview and look in the data directory for it.
And I create a little NSBlock operation. But all this operation does is get the appropriate cell and apply an image to it using the contents of the preview URL. And I also have this little guy, which is a dictionary of preview loading operations, this actual NS block operations. I'm going to use that in a little bit for the purpose of actually being able to cancel this operation if the cell becomes no longer on screen or I reload data, something like that.
So all I need to do is go to a background thread. So I dispatch async to the global queue. And I use an NS file coordinator to coordinate reading to the preview URL. And that operation, coordinate reading to the preview URL, will result in downloading the preview file. So I download what hopefully is a very small file while not downloading my document. And when that operation is done, on the main queue, I can enqueue the preview loading operation that I created up here, and that will go and apply the image to the cell.
The last little bit of making this code great is implementing a delegate method on the table view. This is actually a new method in iOS 6 for UITableView. That's didEndDisplayingCell, and that tells us when the user scrolls a cell off screen or when reload data is called, we no longer need a cell.
For any reason that a cell gets reused, or just taken out of the table view, we call didEndDisplayingCell, and we can use this opportunity to take the reference to that block operation that we had and cancel it, and then remove it from the dictionary. So, Now that I should have preview, let's try this. So there's, I now have previews popped up in this document. And I can edit this preview too. You know, for my WWDC, maybe I wanna change this. I think about this as more of an Xcode kind of thing. Oh, I don't have that on here. Maybe I have it on here. Let's see. Yeah, I've got some images on here. We'll also get to see this sync across and it'll be fun. So I'm gonna change, it changes the preview and momentarily we should actually see the preview come up over here. Ta-da.
Sync. Oh, I didn't save it. That's fine. Updates the preview file over there. And, hey, it even syncs the clock because I'm in iCloud. Woohoo! All right. So that's preview. It's not that hard. Write out the data in the background thread using UIDocumentOverride. And then we just download that individual preview file without downloading the entire document. Save network, save storage on the user's device.
It's a great technique. All right, it's time to talk about The last things we want to think about to polish our app and get it ready to put on the store and maybe think about some of the ideas that are what we would consider being a good iCloud citizen. And being a good iCloud citizen is really about handling conflicts in addition to using, trying not to use a lot of network bandwidth, which we've talked about a little bit already.
But conflicts, I'm sure you've heard about this before if you came to this talk last year or if you've been to other iCloud talks. It's always gonna be reiterated 'cause we simply can't avoid in a cloud-based world the idea of conflicts happening. We've got all these devices all living under the cloud all at the same time. User can be making edits on any of them, but any of them may or may not be connected to the network at any given time. That's the nature of the beast. So if we end up in a situation where we make different changes on different devices and they're not connected to the network at that time, but eventually they do get connected to the network, at the time they get connected to the network, we've got a conflict. So, how do we handle it? Well, In an ideal world, we have to, well, in a definite world, we must always handle conflicts. And the reason for that is the way that iCloud manages conflicts is when it detects them, it creates a version that is, it decides it's effectively the winner of that conflict, and that is the current version of your file on disk. That's what you will read if you go access your document even after the conflict has occurred. whatever iCloud has decided is the winner, which is basically the last one.
iCloud will also pick a conflict loser and create a new version of the file that represents the conflict loser. You access that through the NSFileVersion API. If you don't deal with conflicts in some way, these conflict versions stay on your user's device. And if you've got large documents, you can build up conflict versions that stick around forever and needlessly waste space. So always handle conflicts. Now, getting back to my perfect world, a great strategy for handling conflicts is some kind of automatic merging. If your application has the smarts to be able to take two conflicted versions of a document, put 'em together, figure out what the right thing is, and save that to disk. Awesome. I will personally give you a high five, a handshake, and a pat on the back.
Nobody has done it. Oh, man. Okay. Well, you'll have it done by next year. And what makes this great is no user interaction is required. It's seamless. The user doesn't even have to think about it. The user doesn't have to understand that a conflict is even a thing in the document. They're just working with your data. And they can go about their day. You handle conflicts behind the scenes. And they're happy.
This isn't always possible. In those cases, when it's not possible, the direction is be lazy about conflict handling. And what we mean by that is don't ping the user as soon as you detect a conflict and force them to stop whatever they're doing and deal with the conflict. I mean, it's great to tell the user that there was a conflict, but don't make it so they can't do anything else before they deal with the conflict. So what we want is maybe something like this, where we show a little bit of UI that lets the user know that they're in a state and gives them a way to access conflict resolution on their own time. So when they decide, "Hey, I wanna deal with whatever conflict happened "and run through whatever conflict handling mechanism "you have created for them," they can do that on their own time. So that's dealing with conflicts.
Let's talk about some advanced error handling stuff you can do in UIDocument. I'm putting this part in there 'cause I think it's a great polished thing, and I think that nobody really knows about what I'm about to tell you. And that's that reading and writing methods in UIDocument, they all return this NSError instance by indirection, but what happens with that? Well, the UIDocument machinery takes that NSError and it sends it to this method, which you can override on UIDocument, which is handleError, user interaction permitted. Great. What does that method do? Well, what that method does is take the NSError and look at it and see, first of all, the error message, but more importantly, does this error have a recovery attempter associated with it? And if that's true, if there has a recovery attempter and the user interaction parameter is yes, then the default implementation of UIDocument will actually put up an alert that shows your error message to the user and gives them the option to invoke your recovery attempter. So you can get in the way by overriding this method in your subclass. And when we send an error, you intercept it. You can then customize that error, possibly by changing the message and by including a recovery attempter, sending your customized instance to UIDocument's default handler. Another way that you can get in the way of this is simply in the reading and writing methods. If you've overridden these, you can create your custom NSR instance there and our machinery will send it over to Handle Error. Additionally, you can also just yourself call Handle Error user interaction permitted at any time.
Or if you don't like our UI, you have your own UI, you can implement Handle Error user interaction permitted yourself do essentially whatever you want. You just need to respect the user interaction permitted flag. So if that's no, you don't put up UI. And that flag would be no in situations like when we're closing the application. If the application is closing, UI doesn't help us because the application's going away.
So let's talk about signing out of iCloud. This is kind of the last architectural thing that we're gonna deal with. And that's the situation where a user's running your app, using iCloud, user goes and deletes their iCloud account. Or maybe they sign into another iCloud account. How does your application deal with that?
The application really just wants to get out of the way. That's all it wants to do. It wants to determine that this has happened by registering for the NS Ubiquity token identity token to change notification, the new notification in iOS 6. This will tell you when that token changed. And when that happens, it just closes any open documents because they don't make sense anymore. If the user had a document in iCloud and the user signed out, Get out of the way. Close those documents. They're no longer accessible.
If the user wants to get those documents back, they will sign back into that iCloud account. Simple as that. And you want to provide user feedback so that the user understands that their data isn't gone. It just requires their nice little credentials, their account name and password and settings to sign back into iCloud and get their data back. So that's pretty easy. We're gonna talk about code for that in a second. But you can write a little alert that looks like this that shows them, hey, you signed out iCloud, sign back in to get your data back. Last thing is, should be the last thing for any application that you ship, but really especially in the iCloud world, that's testing.
You can do testing effectively in iCloud by using airplane mode to create conflicts. That's something people ask about a lot, like, you know, how do I test a conflict pass? Well, airplane mode is a great way to do that. Take two devices, put one of them on airplane mode, make changes on both devices, bring the one in airplane mode back online. When they get to talk to each other again, conflicts come in. This is a great analog for user scenarios because airplane mode stimulates simply being out of contact with the network for a while, and that's a great simulation. Thank you.
Basic changes, things you can do to your document while that document is open on one application. So that's as simple as have the document open on one app, make a change from another device, and make sure that syncs over properly and everything happens correctly on the remote device. Also, while a document is open on one device, change the name of that document from another device.
You can also delete the document while it is open on one device. You can also do all of these things rather than while the document is open and running, while the document is sort of conceptually open but your application is waiting for state restoration. And if you're expecting sort of that document to come back up when you do state restoration, you can exercise all of these things, changing content, moving and deleting.
and then see that your state restoration machinery handles it correctly. The last thing there is use network activity monitor if you are interested in looking at the performance of your application while it's doing all these things. It's great if you get all the right things happening at the user level, but you might also want to ensure that you're not creating unnecessary amounts of network activity and using bandwidth in the process, maybe syncing more data back and forth than is actually required. So I'm going to show you one last demo. And that's the code for responding to sign out in Cloud Notes. So we're going to go back to our application delegate. And so we registered for the NS ubiquity identity did change notification up here and application did finish launching.
And the selector that we use for that is check user iCloud preference and setup if necessary. That's the same thing that we called up here. So the idea is this is a method that we run when we launch our application, but we also run it any time the iCloud state changes.
And to make sign-out work great, if the user is set up having chosen to use iCloud, we're going to make sure we check the ubiquitous token, which is something that we're going to store also in NSUserDefaults alongside the user's iCloud choice. So that method is this thing that looks in NSUserDefaults, takes token data, unarchives it to create a version of the ubiquitous token. And then our logic is, oops.
if that token has changed, and we used to have a token, meaning in our previous time we went through this, we were signed into iCloud, then we have signed out, and we should let the user know that this happened. We use a UIAlertView for this again. Then in our backup and check user iCloud preference and setup if necessary, a little bit of work we do here is check whether -- if the user is not signed into iCloud, we make sure we set our Cloud Manager to not using iCloud. That way all the other bits of our application get to know about that state.
And then -- hopefully that works. Yay. we actually remove from the NSUserDefaults our user choice. And the idea for this is if you sign out from iCloud and then you sign back in later, we'll actually want to ask you again if you want to use iCloud now. So we have to switch to local storage when they sign out, but when they sign back in, we may want to switch back to iCloud storage. So the last bit to make this all work is to store the Ubiquiti token, and that's just in NSUserDefaults. right down here. We take a look, and if we have a token, we'll go ahead and archive it up, set it in NSUserDefaults. My key is UbiquityIdentityToken. If we don't have a token, we'll just go ahead and remove that key from NSUserDefaults. And let's go ahead and run our application. OK, so here's our app. Let's try signing up.
I can short circuit this operation pretty easily by going into documents and data, turning that off, documents are off. Go back to Cloud Notes, pops me up an alert that says, "Hey, I've signed out. "I can sign back in if I want to retrieve my documents." I can see that, hey, I'm just down to local stuff now, and I don't get to use the cloud.
I can sign back in though, which is really just turning documents and data back on. Go back to Cloud Notes. I'm re-presented with the alert that asked me if I want to use iCloud. I do. My data comes back, I download the preview files, and put them back up. I can even access a file that'll download that from the cloud, and my content comes back. Everything's great just by signing back into the cloud.
That's it. If you have any other questions about using iCloud, we've talked about high-level concepts, key things that'll make building an iCloud application easier and make it work for you. We're working on getting you the code that I just wrote today so you can see other pieces of it, like how the little cloud manager singleton works and such like that. If you have additional questions, you can email jury.apple.com. Our evangelist, Mike Driewicz, always happy to help you. We have some additional sessions. Thankfully, these are all still yet to happen. A couple this afternoon on using iCloud with NS document if you're interested in iCloud on the desktop. Using iCloud with Core Data. I know a lot of people are interested in that. And Advanced iCloud Storage tomorrow. Good luck to you in writing fantastic iCloud applications. I can't wait to see them.