App Frameworks • iOS • 54:39
iCloud Storage enables iOS 5 apps to store documents in the cloud, so users can access the same documents from iPhone, iPad, iPod touch, Mac, or PC. Learn how to use the new UIDocument class to build applications that integrate with iCloud.
Speaker: Luke Hiesterman
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.
My name is Luke the Hiesterman. I'm here to talk to you today about storing documents in iCloud using iOS 5. This is going to be an introduction to the new API that we're introducing in iOS 5, UI Document and UI Kit. So I'm going to start by telling you about what we're going to cover today. There's going to be four sections to this talk. Number one is figuring out what is the problem space that we want to solve.
[Transcript missing]
Document-based applications are things that you might find in the iWorks suite, applications like Pages and Keynote and Numbers. These are documents that--or these are applications that present data to the user in a format that they can open it, make changes, and persist those changes to disk, things that you might think of as files on Mac OS X.
Of course, we don't really present files to the user on iOS, but it's that same concept. Now, some of you maybe have never built a document-based application for iOS. And if you're thinking about building your first one, there are some challenges that are inherent to building any document-based application. Let's talk about what some of those are.
First and foremost, any document-based application has to figure out how it's going to do saving and opening, how it's going to read and write its data. There are two basic answers to this question that any application can have. Number one is doing reading and writing on the main thread. This has the advantage of giving you pretty simple code. You'll probably be able to write it quickly. You'll be able to easily understand what it does. And you'll be able to maintain it easily.
The unfortunate side effect of this approach is that if reading or writing takes a non-trivial amount of time, then your application is going to block user interaction. While the main thread is hung writing or reading to or from disk, then the user can't interact with the phone. They may be trying to push buttons or interact with your content in some way, but they can't do that because your application is busy on the main thread writing or reading data. So the solution to this, answer number two to how to handle saving and opening, is using concurrency.
If your application does reading or writing in the background using concurrent threads or queues, then it frees up the main thread so that the user can get the experience that they really want, which is to be able to continue to interact with their phone. I mean, that's very important on iOS. We don't ever want a phone where the user can be pushing buttons and nothing happens.
So dealing with concurrency allows you to solve this problem, but it has the unfortunate side effect of now your code is more complex. It's probably going to take you longer to write, it won't be as easy to understand, and it'll be more error-prone. But once you've solved these problems or figured out what your answer is going to be, you still have to figure out, when should I save? This is an interesting question on iOS because we like to have what we call a saveless user model.
That is to say, we don't like to have a user model that's not going to be able to save. We like to have save buttons on iOS. Rather, the user simply interacts with their content, and as they make changes to the data, we just give them the illusion that it's always persisted to disk, and they don't have to think about when they should save. It's always saved.
So some of you maybe have actually already given answers to these questions. You've written applications that have solved these problems, and you're here wondering, should I adopt UI Document in iOS 5? I think the answer to that question is going to be yes. That hinges largely on the fact that we have this cool little new feature in iOS 5, and we call that iCloud. And more than that, we actually have this really big new feature in iOS 5, and we call that iCloud.
UI Document is built to make your applications have documents that seamlessly integrate with iCloud. If you already have a Document application, you're going to want to bring it over to use UI Document so that you can take advantage of our built-in integration to iCloud that doesn't require any special code on your part.
Now, iCloud introduces some new challenges that didn't exist before in document-based application. We'll talk about three of those right now. Number one is any document-based application needs to be able to respond to updates that come from the cloud. At any time, iCloud may sync some new change that your document didn't previously have. And if your document is open, the user interface needs to be immediately updated so that the user sees the new state of the document that just came down from the cloud.
Number two, your document-based application needs to do reading and writing in a way that is safe relative to the cloud. Because updates can come from the cloud at any time, and likewise, we can take your data that is on disk and sync it to the cloud at any time, we have an iCloud syncing daemon that needs to have access to the file that your document represents at any time, and likewise, your application needs to have access to that file. And so, we need to mediate the iCloud syncing daemon and your application's access to the file in a way that is safe. And thirdly, applications in iCloud need to figure out how to discover conflicts.
Conflicts can come at any time because we always have the possibility that somebody makes simultaneous changes or we have a device that goes offline, makes some conflicting changes come back online. Applications need to be able to discover conflicts so that they can begin the process of conflict recovery and resolution.
So we're going to spend the next 50 minutes or so talking about our solution to all of these problems, which is UI Document. And the really great news is UI Document solves all of these problems that we've just talked about. UI Document handles concurrent reading and writing for you in a way that doesn't make your code complex. It has a built-in autosaving engine that allows you to get a save this user model for free. And it has, of course, built-in iCloud integration.
So let's jump into section two of our talk, which is the basics of UI document. I'd like first to introduce you to what it is that UI Document provides. UI Document is, at its heart, a model base class that represents a document in your application. Now that means you're not going to instantiate direct instances of UI document. Rather, you're going to be subclassing from UI document, and you will be creating instances of your document subclasses.
So what the base class, UIDocument, brings along with it is a few key document handling features. Among those three are number one, concurrent background reading and writing. I mentioned this before. This is very nice that we take all of the work of reading and writing concurrency out of your hands, doing it for you, so that you get to have the simple code that only works on the main thread. You don't ever have to deal with concurrency yourself. But you get the best user interaction scenario, because the main thread will never be blocked, making it so that the user can't deal with their content.
Number two, we have a saveless user model. So we will run the autosaving engine for you. All we're gonna ask is that you tell us when the user makes changes, and we'll do everything for you in terms of ensuring that those changes are saved at the important times when they need to be saved.
And finally, as I mentioned before, we have iCloud integration. Let's look at UIManagedDocument for just a second. This is the one concrete subclass that we provide of UIDocument. This is something provided with CoreData in mind, and it has a few nice features if you want a concrete subclass of UIDocument to get started right away. Number one, it has CoreData integration. So if you have a CoreData stack in your application already, or you're considering representing your model with CoreData, that works great if you have large amounts of data. I encourage that, and that's integrated directly into UIManagedDocument.
UIManaged Document is optimized for iCloud syncing. This is particularly why it's good to think about adopting UIManaged Document in Core Data if you have large amounts of data that your document is dealing with. And finally, UIManaged Document has automatic conflict resolution built in. We're going to be talking later more about dealing with conflicts and conflict resolution, and it'll start to become apparent why it's actually really nice to have somebody do conflict resolution for you. and UI Managed Document does that.
So I'd like to take a second to look at the architecture that your Document application is going to have if you're using UI Document. So you're probably all familiar with the Model-View-Controller paradigm. It's going to be the same thing here. Your application is probably going to have a UIView controller that will represent the controller of your application. And then it's going to have a UIView that that view controller will own.
And that view is going to display your data to the user. And then you're also going to have a model. And that model is going to be your UIDocument subclass. We actually refer to this as a model controller because it's going to encapsulate the model that your document represents.
And so we're going to be focusing on that UIDocument subclass, building that model controller today. So let's jump into reading and writing, what that is. I want to hit one more time. We've talked about that all reading and writing happens on a background queue managed by UIDocument for you. Reading is triggered by an open operation or by a revert operation, which can happen when a change comes in from the cloud. Writing is triggered when we save. Saving most of the time will be initiated by UIDocument as a part of our autosaving mechanism.
And both of these operations, reading and writing, have automatic handling for a couple of built-in data types. Those data types are NSData and NSFileWrapper. These correspond respectively to having a flat file or a file package. NSData will be used if your file is represented in the file system as a flat file.
But if you have a file package, we'll use NSFileWrapper. A file package, for those of you who don't know, is a directory that contains several files, but that is treated, from the user's perspective, as a single file. And we wrap that all up in NSFileWrapper. It's just a moment of an aside.
If you haven't thought about using file packages in NSFileWrapper before, I will encourage you to look into them and consider adopting them, especially if you have an application that stores a lot of data as part of its document. They have optimizations such as faster writing if you're only writing one or a couple of files in the FileWrapper, and likewise, more efficient syncing to iCloud if you're only modifying certain files within the FileWrapper. So with that, let's take a closer look at reading. Thank you.
We have two queues as part of our reading. The one that the reading is initiated on, and the background queue that's managed by UIKit. Probably the only one you're going to care about is the main queue, the queue that you'll be calling us on. And reading begins by you asking us to open the document. Often this will be immediately after you've initialized the document, you'll ask us to open it. And that's done by calling open with completion handler. It takes a block parameter that is the completion handler. we'll invoke at the end of the open operation.
When you ask us to open a document, we will go immediately into the background queue and begin the reading operation. You see that reading sort of takes a long time, potentially, and that's why we do it in the background queue, so that we're not blocking the main thread while this is happening.
At some point, the read operation will complete, and we will come back to the queue that you called us on and ask you to load the contents that we just read into your data model. This is where we provide the one override point that you will need to facilitate reading in your application, and that override point is load from contents of type with an error return by indirection.
As I said, this is your one override point that you will want to implement to make reading happen. And the contents that we send you is an id parameter. And that will be either an NSData instance or an NSFile wrapper instance. If your file is represented on disk as a flat file, we will send you NSData. If it is represented on disk as a file package, we wrap that up in an NSFile wrapper instance and send you that. Whatever your contents is, you can load that into your data model at that time and likely update what the user sees as well.
After you're done loading the contents and you return, we will invoke the completion handler that was passed into the open with completion handler method, and that finishes the reading operation. So writing is pretty much the same thing, kind of in reverse. Again, we're dealing with the same two queues, the one that the writing operation is initiated on, and the queue managed by UIKit to do the actual writing to disk.
Writing begins with a save operation, and that's the method saveToURL for saveOperation, and that also takes a block completion handler. Most often, you're just going to let this get called for you because UIDocument will call this as part of our autosaving engine. And you've simply told us about the changes, and we'll do autosaving. But you will call this if you're creating a new file.
So once save is called, we will immediately turn back to you now on the same queue that save is invoked and ask you to snapshot your data model. So what this means is we need a representation of what your model is right now that we're going to go write to disk.
And this is your one override point to facilitate writing in your application. And that override point is contents for type with an error return by indirection. Now, this has an id return value. And again, if you deal in the built-in supported types of NSData or NSFileWrapper, then you're done if you return us an NSData or an NSFileWrapper, because we know how to write those types to disk. And we'll just go do it. If you want to return a custom-- So, in order to use the new UIDocument class, you have to use the new UIDocument class.
And then when we're done, we'll invoke the completion handler that was passed to savedURL back on the queue on which that operation was initiated. And that's writing. So reading and writing, the big key here is each one just has a single override point. One for reading, which is load from contents of type, and one for writing, which is contents for type.
Just by implementing these two methods, you will have reading and writing based on UI Document in your application, and it will all happen in the background, so you'll get the best user experience. After that, all we need to do to make our application feel full-fledged is make autosaving happen.
And autosaving is all about you telling us about the document's changes. When the user makes edits to the document, you want to tell us about them so we can figure out when to schedule autosaving and make that happen at the right time. So there are two ways that you can tell us about--two easy ways that the user has made changes.
Number one is simply to take that user edit, turn around and call a method on UIDocument, updateChangeCount. This tells us that, hey, a change has been made. We can go schedule autosaving at an appropriate time. Alternatively, if you are supporting undo in your document application, then you can simply register your change with the undo manager that is built into UIDocument. There's a property on UIDocument called undoManager, and you can register your change with that undo manager, and if you do so, we will notice that you registered that change, and we will call updateChangeCount on your behalf.
So it's the same amount of code that you have to write if you're going to support undo. Simply register your change with the undo manager, and that also tells us that you've made a change to the document. Either way, now we're good, and our autosaving engine can take over from there and ensure that the user's data is persistent to disk at the appropriate time.
So that's pretty much it as far as what you need to know to go write a document application that can open, save, and in fact autosave just with a few APIs. And so I'd like to show you just how little code and just how easy it is to go get started right now building an application based on UI Document.
So the application that I'm going to show you today is something that I call Cloud Notes, a very simple text document-based application that actually gives the user the opportunity to sync their documents into iCloud. And this is my UIDocument subclass we see in front of us called NoteDocument.
And you see I just have two methods here that I'm overriding. Load from contents to do my reading and contents for type to do my writing. And with a little simple code in there, that will be all I need for my document subclass. So I'll start with load from contents. I'm past the contents parameter, which in my case is going to be an NSData instance representing the contents of my flat text file, since I'm just dealing with text note files.
When I get that, I'm just going to literally load that into my data model. My data model is simply a string Ivar represented by the document text property. So I'll take that contents as an NSData and Go ahead and set that to my document text model, locking in a string with that data contents.
And I'll deal also with the other possible case that I have an empty file, so I got empty contents from that. And if that's the case, I'll set my model to be an empty NSString. After I've loaded my model, one thing that I want to do is let my view controller know that my model has been updated because, well, I'm going to want to update the view in response to updating my model. So for this document class, I actually created a delegate method that allows me to inform the view controller that my contents have been updated. And I'll just go ahead and call that.
I call noteDocumentContentsUpdated so my view controller knows, "Hey, my contents have changed. You should take the new model and update the view." And with that, I'm done with load from contents, and I can just return that, yes, I have, in fact, loaded from contents. And turn my attention to contents for type.
In Contents for Type, I'm just going to do effectively the reverse of what I just did in Load from Contents. I'm going to take my model, which is just a string in this case, and wrap it up in an NSData and return it to the document. So a little operation that I'm going to do before that is handle the case that I have an empty model right now that my text is zero, has a zero length.
And in that case, I'm going to put some base data in my document because I don't like to save empty documents. I like to have some base data in there. And so I'm just going to put the new note text into my model. Now, that done, I just take my model, which is my document text, and wrap it up in an NSData using data with bytes and return that to the document. It will take my data instance and handle writing it to disk. And everything is done.
In this one screen, you see all the code I'm going to write for my Document subclass, minus some comments at the top that say "created by me." So I'm going to turn my attention for the last bit to the view controller that I have. I'm going to implement just a couple of methods here that are delegate methods. One is the method that I just sent from my document saying that the document contents are updated, note document contents updated. We use this as an opportunity to update my document's view.
So that's simply a matter of taking the model data, which is a string, and putting it into my view. My view is a UI text view, so I can just take the string from my model, put it in the text view, and that will be all I need. for no document contents updated.
And the other method, the last method I want to implement, is a delegate method for the UI TextView. TextView did change. And this is my opportunity to do the reverse operation here from the ViewController2. That is, when the user makes an edit to the TextView, I will get this message. And I want to update my model, and then also tell the document that I have updated my model. So a couple lines of code here to make that happen.
I update the model by setting the document text to the new text of the text view. And then the last thing I need to do is just tell the document that I have updated the model. This is the key to facilitating autosaving so that we know a change has been done. And I just call update change count on the document.
Now I probably could use an undo manager here. My users probably wish I used an undo manager. That will be version 1.1 of my application. And that would call update change count for me. But for now, I've done update change count. So that's all the code I'm going to write.
So, to show you this app in action, this application is-- just a text-based application that will give us a list of documents that are text documents that we can read, make changes to, and enjoy their auto-saving. My application should be launching in just a second here. Xcode says it's running. OK.
So here's my list of documents. I've only got two in this particular app. And they're just text documents, but I can make changes to them. And this is just a list of books that I want to read. Say maybe I want to read Huck Finn as well. I'll go ahead and make that change.
add that to my list. I can go look at my other document. Remember, oh, I definitely need to get bacon when I go home. And I switch back to my other document. And my content is still there because, well, I told the document that I made a change and it auto-saved it for me. So I didn't have to do anything. When I reopened the document, my content is there for me. It was auto-saved.
And that's great. There's one other little nicety to this application that I didn't even have to write any code for that I'd like to show you now. And that's that I have another instance of this application on my phone. And maybe I want to make a change to that. Maybe I want to remember that I want to read the book at home when I get home.
So I'm going to make that change on my phone. And-- It's going to auto save on my phone after it saves. It's going to sync up to iCloud. iCloud is going to sync it down to my iPad. And when that happens, you'll see that my iPad just updates.
And you know what my favorite part of that is? I didn't write any code to do that. UI Document did that for me. All I did was implement the reading method, and Document knew when I had to reread the document because it was updated in iCloud, and I'm done. So that's really just how easy it is to get started writing document applications and even having them be ready to integrate with iCloud. I should mention-- that you do need to individually set, in the real world, documents to sync to iCloud via NS File Manager APIs.
So you set individually the document to be ubiquitous and to sync to iCloud. In this case, I had done that beforehand in this demo, so full disclosure, I had said these documents should be syncing. But once you do that, once you use the NS File Manager APIs to say these documents should sync to iCloud, then UIDoctument is fully ready to do the right thing for you and make your application syncable. So that's how easy it is. Let's move to section three of our talk, which is handling conflicts and errors in UI Document.
Unfortunately, with any document-based application, there's always the possibility of things going wrong. We can't just guarantee that life is going to be peaches and roses all the time, because, well, hey, maybe your disk is full and you can't write. Or maybe a conflict occurs in the cloud. And we can't just guarantee that these things won't happen. But what we can do is try to give our users the most graceful experience when those unfortunate things do happen. So that's what we're going to talk about now.
Now, what UIDocument provides for you to help make this a little easier is a property called DocumentState that you can observe to know when the documents in various states that tell you about when the user's changes are being safely So the document state tells you about that information of how safe it is to let the user make changes. And we'll be observing this property through a notification, which is UIDocumentStateChangedNotification. And you'll probably register for this notification in your view controller. This is because responding to DocumentStateChanges is all about giving feedback to the user.
The user is going to want to know if we're having a problem saving, for example, and their data isn't being persisted to disk as their normal illusion of, "Hey, I just make changes and they're persisted for me." So we want to let them know when we're in those interesting states.
Let's talk about what those document states are. Most of the time, your document is just going to be in UIDocumentStateNormal. And this means, well, everything's fine. User changes are being persisted to disk as they would expect. Everything's hunky dory. They make changes. We save them. Life is good.
There are four other states that the document may be in, and we'll talk about those and what they mean right now. Number one is state closed. This is the initial state of the document when--after you initialize it. It will remain in this state until you successfully open the document or you save if you're creating a file.
And obviously, the user probably isn't making changes when the document is closed. But we're certainly not autosaving or anything like that, it's a closed document. The second state we can be in is the conflict state. This state is set when we discover a conflict in iCloud, and this gives you the opportunity to let the user know that, hey, there is a conflict, and you may want to resolve it. We'll talk about initiating the conflict resolution process in a little bit.
Number three is saving error. This means... The last time we tried to save the document, it wasn't successful for some reason. There was an error. We want to let the user know that, hey, your data actually isn't being persisted to disk in a safe fashion right now. And they're probably going to want to know about that.
Number four is editing state disabled. This is the only one of the states that is explicitly disallowed to let the user make changes to the document while we're in this state. If we're in a saving error state or a conflict state, for example, you might let the user continue to interact with the document, especially in a conflict state.
They may choose to continue to make changes and deal with that conflict later. But in an editing state disabled or state editing disabled, we're telling you that it is actually not safe right now to let the user make changes. An example might be when we revert the document. While we're in the middle of a revert operation, we know that any changes at the moment are just going to be thrown out as soon as we complete this revert. So it just doesn't make sense to allow them to make changes.
So let's revisit the document application architecture that we saw at the beginning. And we focused mostly on the UI document and building our model controller so far. We're going to take a moment to focus on the view controller for this portion of the talk, because as I said before, this is all about providing feedback to the user. So some user interface that says, hey, the document's in an abnormal state. That's what this is all about. So let's walk through what we might do to respond to a saving error.
You start by registering for the document state change notification. You'll do that on your view controller, because your view controller is going to want to provide UI in response. We'll call this with the saving error state set if an error occurs. And your response to that is going to want to be to show some non-modal UI to the user, indicating that they're in this state.
The key here really is non-modal, because what we don't want is just popping modal alerts in the user's face. Particularly considering autosaving and things, we could have repeated errors, and we don't want UI alert views popping up repeatedly, yelling at the user, telling them, hey, there is an error. We get it.
We want to communicate this to the user, but we don't want to yell it at them. So non-modal UI is the key. And we'll show you an example, maybe some food for thought, of what this means in just a little bit. I'd like to talk about conflict resolution. Conflict resolution, of course, is a little bit new for iOS 5.
But it's this world we are now going to live in with iCloud where, hey, we've got multiple devices around that all may be making changes relative to each other, and those could sink to the cloud and cause conflicts. So there's always the possibility that I took my iPad on the plane with me, and while I was there I decided, eh, I want to add something to my grocery list. I want to make sure we get mustard.
But at the same time that I've done that, my aunt Edna, who's coming to pick me up from the airport, added ketchup to the grocery list. She thought we needed ketchup. And then when I get off the plane and get internet again, our changes both try to sink up to the cloud, and we have these conflicting changes.
We have mustard and ketchup. Well, which one should it be? I don't know. We have to solve this in some way. But the key to this story is you can't make your users not go on planes, not go offline. And thus potentially make conflicting changes, or even just make changes simultaneously on two devices if they're like that.
So what I want you to be thinking right now is if you're wondering whether your application needs to have a conflict resolution strategy, the answer to this is if you are going to integrate to iCloud, yes, you must have a conflict resolution strategy, because it can happen, and if your users have multiple devices, odds are maybe at some point it probably will.
So we're going to talk a little bit in a bit about how you're going to do that, but first of all, finding out that there is a conflict is pretty similar to finding out that there's a saving error. You're going to get that notified via the document state change notification, and in this case we'll have the state in conflict set.
Once that happens, you'll turn around and you can look at the conflict versions that exist via NSFileVersion APIs. And after you execute some sort of conflict resolution strategy, you will then potentially revert the contents of the document if you've changed the on-disk representation of the file. For example, if you've done a merge or you've copied some other version into being the current version, then you'll need to revert the document so that we get what we see on screen equal to what you put on desk.
So let's talk about how we can actually think about resolving some conflict. What are some strategies that we can employ? And there are a few of them. Number one, this is my personal favorite, if you can do it. And that's automatic merging. And that is taking the changes that the user made in different places and bringing them together in a way that makes sense for your document without having to-- Bring in any user interaction. You just figure out what makes sense and do it.
In the case of the ketchup mustard incident that I showed to you before, what the application probably should have done is said, hey, one person wants ketchup, one person wants mustard. You know, our final version of the document should just have ketchup and mustard both in it. It doesn't make sense for all documents, but for some it certainly does, and if you can do it, do it.
Another solution not so good is just take the latest version. Maybe this makes sense for your document. What that is is when iCloud picks a winner, a conflict winner, and makes that the current version, you can just say, okay, yeah, I like that, and turn around and say all of the conflict loser versions that exist out there are--I don't need them. Just mark them as resolved. That's part of the NSFile version API. You can just say, hey, this version is resolved.
and just be done with that. It has a downside of potentially appearing to the user as a data loss, because if my mustard version, for example, was declared by iCloud as the loser and you just go, mark it resolved and don't tell me about it and it goes away, well, I don't know what happened to my mustard.
But at least it's better than nothing, because if you don't at least mark conflict versions as resolved and have some sort of conflict resolution strategy that way, you effectively introduce a leak, a data leak, into the user's iCloud account and onto their device, because the conflict loser version stick around until they're marked as resolved.
So that's why it's really bad to just do nothing. You don't want these conflict versions sitting around forever. We've got to do something. Another naive approach is, well, just prompt the user. Maybe you don't know how to merge the changes. That can be OK. That makes sense sometimes.
And if you don't know, you just say, hey, user, I've got a conflict. Sorry that happened, but can you let me know what I should do about this? And you provide some UI that allows the user to do manual merging or pick a version that they want to keep. Whatever it is, give the user a chance to do that.
The last solution I have in mind today is a nice easy one, and I mentioned it earlier. Let's just use UIManagedDocument. UIManagedDocument, as I said before, has built-in conflict resolution for you. So if you just adopt CoreData and use the UIManagedDocument class, this is done for you. Nice, easy day. Conflict resolution is not your problem.
So consider using that if it makes sense, and you can learn more about that in What's New in CoreData on iOS, which was talked about yesterday. If you missed that, we post videos online for a reason. So to give you an example of this kind of thing, I'd like to talk about what we can do to the Cloud Notes application that I already showed you to employ some of these ideas. So what we want to do is add some UI to express the state of the document to the user in Cloud Notes. So what I am adding here is a little light.
In my application above my content, in this case it's a green light that tells the user, hey, green light, your document is in the normal state. This is reflective of your document's state normal. And whatever you're doing, making changes, they'll be automatically saved to disk. Everything is A-OK. Good times for the user.
Okay. So, if I encounter a saving error, I can non-modally just turn my light to a red light and add the text, "Saving error," or unsaved, yeah, unsaved. And that tells my document, "Hey, this document isn't saved." That's not normal, because I normally just expect my data always to be saved and always persisted.
But it's certainly good to know that, hey, I'm in this state, and, you know, maybe it's interactive for you, maybe the user can tap on that and get some more information. But the real key here is that I didn't pop up a modal alert in the user's face.
I just gave them some kind of non-modal feedback that, hey, we're in a state where we actually aren't persisting to disk. Another case would be we discovered that a conflict, the conflict--the in-conflict state was set on our document. And what I do here is just turn this to a yellow light and put a "Resolve conflicts" button on there. I've decided to employ the prompt the user approach for this application. And so I'm letting the user know that, hey, we're in a conflict state. You can actually continue to make changes if you want. Just continue to interact with the document.
When you decide, "Hey, I want to resolve those conflicts," they can tap the button. I'll show them some UI that allows them to manually merge the conflicts or pick a version, whatever they think the final version should be. I will then make sure that's saved and revert the contents of the document.
So that's the polish that we would want to add to our application to get it to that level where it's ready to ship to the App Store. We've not only done opening and saving and we've enabled the auto-saving engine by telling the document about the changes we've made, but we've also added support to our document to handle when things might go wrong and make sure that the user is protected and is going to have a relatively graceful experience no matter what happens with our document.
So we have some time now to delve into the advanced concepts that exist in UI Document. This is going to be a relatively brief overview to give you an idea of what's out there to gain more fine control, more fine-grained control over UI Document and really affect how it behaves. What I want to reiterate before we get into this is that we've provided some really simple API for you that is what we've discussed up to this point in the talk that really just should be everything you need to go ship your application.
And I only encourage you to go looking at these fine-grained hooks that we're giving you if you find that your application really has some specialized need that just isn't fulfilled by the API that we've shown thus far. And if that's the case, okay. I will send you to the docs. That's after this talk because we don't have much time to talk about advanced things, but we'll do what we can.
So let's revisit the queue model that we had. For reading, it looked like this. We initiated opening on the calling queue. We went and did reading on a UIKit managed background queue, and then loaded the contents and executed the completion handler on the calling queue. Writing similar. We're doing snapshotting on the main queue after saving, writing that in the background, and executing a completion handler on the main queue.
We're going to focus for the next few minutes on things that exist in the UIKit background queue, and what you can do to call into that queue, to initiate specialized operations, or to override methods that get executed in that background queue. So there are a couple of considerations that you'll want to keep in mind if you're delving into this area.
One is, if you're overriding any of these methods, you need to be aware that, hey, I'm dealing with something on a concurrent queue, and so my code that I'm writing in one of these overrides needs to be thread safe. That's the simple thing. It gets actually more complex when we're talking about calling into one of these methods. There are a couple of things you need to do if you call into one of our background reading or writing methods.
Number one, you need to take whatever it is you're doing, whether it's a read from URL operation, or whatever, or whatever. and wrap it into a block that you pass to perform asynchronous file access using block. This is a method on UI document that allows us to schedule your operation on our background reading and writing queue, which is where all reading and writing needs to happen.
Additionally, you will need to use the NSFileCoordinator API if you are doing reads and writes initiated yourself, rather than calling our high-level methods, open with completion handler and save to URL. Those methods do file coordination for you, which is necessary to have safe reading and writing relative to the iCloud daemon.
But if you're doing these things yourself, you need to use the NSFileCoordination API, which is also new in iOS 5. And there was a talk on that, taking advantage of file coordination, also yesterday. The video will be posted online. I really encourage you to watch that video if you at all are in the situation where you're needing to do file coordination.
So one of the things you can do if you are in this world where you need the fine-grained control and you're going into this background queue is incremental reading. So if this is our diagram of how the reading queue model works, we're going to focus now finally on that background queue and give you your override point for doing incremental reading. This is actually a method that you will call and override, more than likely, if you're doing incremental reading.
Read from URL with an error returned by indirection. The reason you will probably do both is you'll want to override it so that when OpenWithCompletionHandler executes, we invoke your implementation of read from URL, your incremental implementation. But then also, likely, when you get to the point where you want to do some more reads, you're going to want to call in directly to read from URL, so you'll both override it and invoke it yourself.
On the writing side, you might want to have your own definition of safe writing. Well, safe writing is something that UIKit, UIDocument does on your behalf any time we do a normal save. Safe writing means, for us, we write your data into an ancillary file on the side of the actual file URL that your document represents. And when we've written all that data out, we atomically copy it into place. That's what safe writing means by default for your iDocument.
But maybe this doesn't make sense for your application, and you want your own definition of safe writing. Or maybe you don't want to participate in SafeWriting. If that's the case, the override point that we're giving you is write contents and attributes safely to URL. And here you can do what makes sense for your application in terms of SafeWriting, if it's something other than writing to an ancillary file, or just skip SafeWriting entirely and write directly to the file in this method. Something else that you might be interested in is writing a custom snapshot of your data.
If you want to do that, you can leave write contents and attributes safely to URL alone, and let our default implementation of that call another write method, also on the background queue, where you can have your override point for writing a custom snapshot. That override point is-- well, the text is wrong here, and I apologize for that. That's the same method you saw before.
The actual method is just write contents to URL. I think it takes a save operation parameter and an error by indirection, and also the original file contents URL. So you can look that method up in the UI document header. It's the one that doesn't have safely in the name.
And that's your opportunity to actually do the actual writing to disk. If you return us something other than an NSData type or an NSFileWrapper type from contents for type, then you're actually required to override here so that you can write that data to disk, since we only know how to do NSData and NSFileWrapper.
So one last thing I have in mind that you might be interested in is changing the file's type. Some applications might want to do this dynamically. Their document class can actually represent more than one type of file, and that type could actually change through the document's lifetime. And we do have some overrides to allow you to do that.
Example might be, maybe you have a flat RTF file, but then the user can add an attachment to it, and it becomes an RTFD file, and then you need to dynamically change the type of that document. And so override that we're providing for you there is saving file type.
We will call this at the beginning of a save operation to determine what type we should use for that save, and you return us a uniform type identifier as an NSString that we'll use for that save. We'll take that uniform type identifier, turn around, and call file name extension for type, All right.
So that's what I wanted to talk to you about with UI Document today. Like to reiterate a few things. Number one, if there's anything I want you to have in your head as you walk out of here and go write your first document-based applications, it's really be simple. Remember the demonstration that we had where I was able to only override two methods on UI Document, load from contents and contents for type.
UI document is designed to encourage you to use the simplest parts of its API that you can. And we have fine grained controls for applications that need to do specialized things. But I really like you to look at doing the simple things and using the simple APIs first and only move to the advanced ones if you find some specialized need that you can't fill with the easy API. It'll make your life easier. It'll make your applications developed more quickly. And if you ever pass them on to another developer, they'll be able to understand your code better.
So great advantages to keeping it simple. Number two is don't forget to register your changes with the document. We love autosaving. We love the saveless user model. That's all driven by you very simply telling us about the changes that are made. It's very easy to do this, either calling our update change count method or registering your changes with the undo manager. And after that, we're off and running. and auto saving happens for you.
Finally, since this is what UI Document is all about, I want you to use iCloud. UI Document was built for iCloud. Users are going to love using iCloud. And the premier applications on the App Store are going to be ones that allow the user to seamlessly have their data across all of their devices. So if you're building Document applications in iOS 5, you really should use iCloud. And thankfully, UI Document makes that incredibly easy. Not really any specialized code to handle UI document syncing.
But the one thing also to remember when you are saying, yes, I am going to integrate with iCloud, is also, yes, I need a conflict resolution strategy. That may be using UI Manage Document. That may be doing automatic merging. God bless you if you do so. Or it may be presenting things to the user and letting them figure it out for themselves. But whatever it is, don't do nothing. You don't want to leak data into the user's iCloud storage account. And you certainly don't want them to have the appearance of a data loss because they can't access versions that have been conflicted away from them.
So that's UIDocument that's storing documents in iCloud. If you're interested in some other sessions, I mentioned a few, taking advantage of file coordination, what's new in Core Data on iOS, and also there's an iCloud Storage overview. All of these sessions already happened, so I apologize for that. I will next time ask to be first. And I really look forward to seeing the great document-based applications you all are going to be writing in iOS 5. Thank you very much.