Wednesday, 7 May 2014

Document Overview [Doc, You Meant Storage]

The word “document”  has many meanings, but in this context a document is a data file (or package)
containing user-generated content that your app opens, modifies, and ultimately saves to persistent storage.
We’rallused to documents on desktop computer systems. In mobile devicesthe concept of a document
takes a backseat, but its still thereand in much the same form. In few apps, like the Pages word proces
sing app, the familiar document metaphor appears front and center; you launch the app, see a collection of 
your named documents, choose one to work on, the document opens, and youbeing typing. In other apps, its not as clear that you’re using individual documents, and some apps hide the mechanics of documents entirely.You can choose any of these approaches for your app. iOS provides the toolsneeded for whatever interface you want, but it doesnt dictate one.

This flexibility lets you add document storage and management to your app completely behind the scenes,
loosely coupled to your user interface, or echoing the legacy document metaphor of desktop computer
systems. Whatever you decide to do with your interface, the place to start is the UIDocument class. Here are the basic steps to using UIDocument in your app:

n     Create a custom subclass of UIDocument

n     Design an interface for choosing, naming, and sharing documents (optional)

n     Convert your apps data model to, and from, data thats suitable for permanent storage

n     Handle asynchronous reading of documents

n     Move documents into the cloud (optional)

n     Observe change notifications from shared documents and handle conflicts (optional)

n     Implement undo/redo capabilities, or at least track changes to a document

You’re going to revisit the MyStuff app and modify it so it stores all of those cool items, their descriptions, and even their pictures, in a document. There are no interface changes to MyStuff this time. The only thing your userswill notice is that their stuff is still there when they relaunch your app!

Where, Oh Where, Do My Documents Go?

So where do you store documents in iOS? Heres the short answer: Store your documents in your apps private Documents folder, and optionally in the cloud.

The long answer is that you can store your documents anywhere your app has access to, but the only place that makes much sense is your apps private Documents folder. Each iOS app has access to a cluster
of privatefolders called its sandboxThe Documents folder is one of these and is reserved, by iOS, for your apps documents. The contents of this folder are automatically backed up by iTunes. If you also want to exchange documentsthrough iTunes, your documents must be stored in the Documents folder.

This is somewhat different than what you’re used to on most desktop computer systems, where apps will let you load and save documents to any location and your Documents folder is freely shared by all of your apps. In iOS,an app only has access to the files in its sandbox and these directories are
inaccessible to the user—unless you deliberately expose the Documents folder to iTunes—or
other apps.

For MyStuff, you’re going to store a single document in the Documents folder. You wont, however, provide any user interface for this document. The document will be automatically opened when the app startsand any changes made by the user will be automatically saved there. Even though you’ll be using the
standard document classes, and storing your data in the Documents folder, the entire process will be invisible to the user.

Thats not to say that you cant, or shouldnt, provide an interface that lets your users see what documents are in their Documents folder. A typical interface would display the document names, possibly a preview, and allow theuser to open, rename, and delete them. You could do that in a table view, a collection view, or even using a page view controller. If document manipulation makes sense for your app, create the interface that presents your documents in their best light.

Where you’ll save your document seems like a great place to start. You’ll begin by creating a custom
subclass of UIDocument and defining where and how your document gets stored.


MyStuff on Documents

Pick up with the version of MyStuff at the end of Chapter 7, where you added an image for each item. In the project navigator, choose New  File ..., from either the File menu or by right/control- clicking in the navigator. Use theObjective-C class template, name the new class MSThingsDocument,
and make is a subclass of UIDocument. Add it to the project. Declare two class methods in
MSThingsDocument.h interface file (new code in bold):

@interface MSThingsDocument  : UIDocument
(NSURL*)documentURL;
(MSThingsDocument*)documentAtURL:(NSURL*)url;
@end

Switch to the MSThingsDocument.m implementation file. Add these constant definition after the
#import statements:

#define kThingsDocumentType             @"mystuff"
#define kThingsDocumentName            (@"Things  I Own." kThingsDocumentType)

Add the first method to the @implementation  section:

(NSURL*)documentURL
{
static NSURL *docURL   = nil; if (docURL==nil)
{
NSFileManager  *fileManager = [NSFileManager   defaultManager]; docURL  = [fileManager  URLForDirectory:NSDocumentDirectory



inDomain:NSUserDomainMask appropriateForURL:nil
create:YES error:NULL];
docURL  = [docURL URLByAppendingPathComponent:kThingsDocumentName];
}
return docURL;
}

The +documentURL method returns an NSURL object with the filesystem location of the one and only document used by your MyStuff app. Theres a little code in there so the location is only constructed once, because it neverchanges.

The important method here is the -URLForDirectory:inDomain:appropriateForURL:create:error: method. This is one of a handful of methods used to locate key iOS directories, like the Documents directory in your apps sandbox. TheNSDocumentDirectory constant tells which one of the half- dozen or so designated directories you’re interested in. To locate directories in your apps sandbox, specify the NSUserDomainMask. The create flag tells the filemanager to create the directory if it
doesnt already exist. This was gratuitous, because the Documents directory is created when your app is installed and should always exist.

Once you have the URL of your Documents folder, append the documents name, creating a complete path to where your document is, or will be, stored.

Now write a method to open your document. MyStuff isnt going to present a document interface. When it starts, it either creates an empty document or re-opened the existing document. Consolidate that logic into a method,immediately after the +documentURL method:

(MSThingsDocument*)documentAtURL:(NSURL  *)url
{
MSThingsDocument  *document;
document  = [[MSThingsDocument alloc]  initWithFileURL:url];

NSFileManager  *fileManager = [NSFileManager   defaultManager]; if ([fileManager fileExistsAtPath:url.path])
{
[document  openWithCompletionHandler:nil];
}



else
{
[document  saveToURL:url forSaveOperation:UIDocumentSaveForCreating completionHandler:nil];
}

return  document;
}

This method creates a new instance of your MSThingsDocument object at the given (file) URL. It then uses the
file manager to determine if document at that location already exists (-fileExistsAtPath:). If it does, it sends the document object an -openWithCompletionHandler:
message to open the document and read the data it contains.If it doesnt exist, one is created by sending the -saveToURL:forSaveOperation:completionHandler: message. The opened document object is then returned to the
sender.

Supplying Your Documents Data

In your subclass of UIDocument, you are required to override two methods: -contentsForType:error: and -loadFromContents:ofType:error:. These two methods translate your apps data model objects into a form that UIDocument cansave, and later converts that saved data back into the data model objects your app needs.

This is also where implementing UIDocument gets interesting. The key is to understand what UIDocument is doing for you, and what UIDocument expects from -contentsForType:error: and -load FromContents:ofType:error:. Theres astrict division of responsibilities:

n     UIDocument implements the actual storage and retrieval of your documents data

n     -contentsForType:error: and -loadFromContents:ofType:error: provide the translation between your data model objects and a serialized version of that same information

UIDocument might be storing your document on a filesystem. It might be storing your document in the cloud. It might be transferring your document over a USB connection. Someday it might store your document on a wirelesselectronic wallet you carry around on a key fob. I dont know, and you shouldnt care. Let UIDocument worry about where and how your documents data gets stored.

When UIDocument wants to save your document, it sends the -contentsForType:error: message. Your implementation should convert your data model objects into data suitable for storage. UIDocument takes the returned dataand stores it on the filesystem, in the cloud, or wherever.

When its time to read the document, UIDocument reverses the process. It first reacquires the data (from wherever it was saved) and passes that to -loadFromContents:ofType:error:, which has the job of turning it back into thedata model objects of your app.

The $64,000 question is “how do you convert your data model objects into bytes that UIDocument can store?” That is a fantastic question, and the answer will range from stunningly simple to treacherously complex. Broadly,you have four options:

n     Serialize everything into a single NSData object

n     Describe a multi-part document using file wrapper objects

n     Back your document with Core Data

n     Implement your own storage solution

The first solution is the simplest, and suitable for the majority of document types. Using string encoding, property list serialization, or object archiving (which you’ll learn shortly) convert your data model object(s) into a singlearray of bytes. Your -contentsForType:error:
method then returns those bytes as an NSData object that UIDocument stores somewhere. Later, UIDocumentretrieves that data and passes it to your -loadFromContents:ofType:error: method, which unarchives/
deserializes/decodes it back into the original object(s). If this describes your apps needs, then congratulations—you’re pretty much done with this part of your UIDocument implementation!

Your MyStuff app is a little more complicated. Its cumbersome to convert all of the apps data—descriptions and images into a single stream of bytes. Images are big and time consuming to encode.
Not only will it take a long time to save the document, the entire document will have to be read into memoryand converted back into image objects before the user can use the app. No one wants to wait tens ofseconds, and certainly not minutes, to open an app!

The solution MyStuff will employ is to archive the descriptions of the items (much like the first solution) into a single NSData object, but store the images in individual files inside a package.
A package is a directory containing multiple files that appears, and acts, like a single file to the user.
All iOS and OS X apps are packages, for example.

Wrapping Up Your Data

You might be seeing the glimmer of a conundrum. Or, maybe you dont.
Dont worry if you missed it, because its a really subtle problem. The concept behind -contentsForType:error: is that it returns the raw data that representsyour document—just the data.
The code in -contentsForType:error: cant know how that data gets stored, nor does it do the storing. Creating a design that states “images will be stored in individual files” is a non-starter, because-contentsForType:error: doesnt deal with files. The document might end up being stored in something that doesnt even resemble a file. It might get put into the records of a database, or become a tag in an XML file.

So how does  contentsForType:error: return an object that describes not one, but a collection of, individual
data blobs,one of which contains the archived objects and others that contain individual image data? Well it just sohappens that iOS provides a tool for this very purpose. Its called a file wrapper, and it brings us to the second method for providing document data.

A file wrapper (NSFileWrapper) object is an abstraction of the data stored in one or more files.
There are three types of file wrappers: regular, directory, and link. Conceptually, these are equivalent to
a single data file, a filesystem directory, and a filesystem symbolic link, respectively. File wrappers allow your app to describe a collection of named data blobs, organized within a hierarchy of named directories. If this soundsjust like files and folders, it should. And when your UIDocument is stored in a file URL, thats exactly what these file wrappers will become. But by maintaining this abstraction, UIDocument can just as easily transfer this datacollection over a network or convert the wrappers into the records of a database.

Using Wrappers

Using file wrappers isnt terribly difficult. regular file wrapper represents aarray of bytes,
like NSData. directory file wrapper (or just directory wrapper) contains annumber of other filwrappers. One significant difference between wrappers and files/folders is that wrapper has
preferred name and key. Its key is the string that uniquely identifies the wrapper, just as a filename uniquely identifies file. Its preferred name is the string it would like to be identified
as. If the preferred name of wrapper is unique, its key and preferred name will be the same. If, however, you add two or more wrappers with identical preferred names to directory wrapperthe directory wrapper willgenerate unique keys for the duplicates. Iother words, its valid tadd multiple wrappers with the same name to the same directory wrapper. One side effect is that adding a wrapper with the same name as an existing wrapper doesnt replace, or overwrite, an existing wrapper, as it wouldon a filesystem.

Your contentsForType:error: method will create a single directory wrapper that contains all of the other regular
file wrappers. There will be one regular file wrapper with the archived version of your data model objects. Eachitemthat has a picture will store its image as another file wrapper. You’ll modify MyWhatsit to store the
image in the document when the user adds a picture, and get the image from the document when it needs itagain.

1Blob is actually a database term meaning Binary Large Object, sometimes written BLOb.

Incremental Document Updates

Organizing your document into wrappers confers a notable feature to your app: incremental document loading and updates. If your user has added 100 items to your MyStuff app, your document package (when saved to afilesystem) will consist of folding containing 101 files: one archive file and 100 image files. If the user replaces the picture of their astrolabe with a better one, only a single file wrapper will be updated. UIDocument understandsthis. When its time to save the document again, UIDocument will only re-write that single file in the package. This makes for terribly fast, and efficient, updates to large documents. These are good qualities for your app.

Similarly, file wrapper data isnt read until its requested. When you open a UIDocument constructed from file wrappers, the data for each individual wrapper stays where it is until your app wants it. For your images, that meansyour app doesnt have to read all 100 images files when it starts. It can lazily retrieve just the images it needs at that moment. Again, this means your app can get started quickly and does the minimum work required to displayyour interface.


Constructing Your Wrappers

Select the MSThingsDocument.m implementation file. Just before the @implementation  section, define two more constants and add a private @interface section that declares two instance variables:

#define kThingsPreferredName             @"things.data"
#define  kImagePreferredName              @"image.png"

@interface MSThingsDocument  ()
{



}
@end

NSFileWrapper       *docWrapper; NSMutableArray   *things;

The two constants define the preferred wrapper names for the archived MyWhatsit objects and animage added to the directory wrapper. The docWrapper instance variable is the single directory wrappethat will contain all of yourother wrappers. For all intents and purposes, docWrapper is your documentdataThe things variable is the array of MyWhatsit objects that constitute your datmodel.

Now add the -contentsForType:error: method to the @implementation  section:

-  (id)contentsForType:(NSString  *)typeName
error:(NSError *    autoreleasing  *)outError
{
if (docWrapper==nil)
docWrapper  = [[NSFileWrapper alloc]  initDirectoryWithFileWrappers:nil];



if (things==nil)
things = [NSMutableArray  array];

NSFileWrapper  *wrapper  = docWrapper.fileWrappers[kThingsPreferredName]; if (wrapper!=nil)
[docWrapper  removeFileWrapper:wrapper];

NSData *thingsData = [NSKeyedArchiver  archivedDataWithRootObject:things]; [docWrapper  addRegularFileWithContents:thingsData
preferredFilename:kThingsPreferredName];

return  docWrapper;
}

This message is received when UIDocument wants to create or save the document. If the document doesnt exist yet, docWrapper and things will be nil. In this circumstance, the code creates an empty directory wrapper andstores it in docWrapper. It also creates a new, empty, things array, which will become the data model for the app.

The rest of the method assembles all of the data UIDocument will need to store the document. It checks to see if the docWrapper already contains a wrapper named things.data. This is the wrapper that contains the
archived version of your data model objects. There should only be one of these, so if it already exists, its firstremoved from the directory wrapper. Remember that adding another wrapper with the same name wont replace an existing wrapper.

The last step is to archive (serialize) all of the MyWhatsit objects into a portable NSData object. I’ll explain how that happens in the nexsection. The datis passed to the -addRegularFileWithContents
:preferredFilename: method. This is a convenience method that creates a new regular file wrapper, containing the bytes in thingsData, and adds it to the directory wrapper with the preferred name. This method saves you fromexplicitly coding those steps.

You return the directory wrapper, containing all of the datin your document, to UIDocument. Now yomight be asking, “But what about all of the image data? Where does that get created? Thats a reallgood question. Image datais represented by other regular file wrappers in the same directory wrapperWhen the document is first created, there are no images, so the directory wrapper only containthings.data. As the user adds pictures to the datmodel,each image will add a new wrapper to docWrapper. When your document is saved again, the file wrappers containing the images are already
in docWrapper! Each regular file wrapper knows if it has been altered oupdated, and UIDocument is
smart enough to figure out which files need to be written and which ones are already current.


Interpreting Your Wrappers

The reverse of the previous process occurs when your document is opened. UIDocument obtains that data saved in the document, and then sends the -loadFromContents:ofType:error: message. This methods job is to turn thedocument data back into your data model. Add this method immediately after your -contentsForType:error: method:

-  (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName
error:(NSError *    autoreleasing  *)outError



{
docWrapper  = contents;
NSFileWrapper  *wrapper  = docWrapper.fileWrappers[kThingsPreferredName]; NSData *data = wrapper.regularFileContents;
if (data!=nil)
things = [NSKeyedUnarchiver   unarchiveObjectWithData:data]; return (things!=nil);
}

The contents parameter is the object that encapsulates your documents data. Its always going to be the same (class of) object you returned from -contentsForType:error:.
If you adopted the first method and returned a singleNSData object, the contents parameter will contain an NSData object with the same data. Since MyStuff elected to use the file wrapper techniquecontents is an equivalent directory wrapper object to the one you returned earlier.

The first step is to save contents in docWrapper; you’ll need it, both to read image wrappers and to later save the document
again. The rest of the method finds the things.data wrapper that contains the archived MyWhatsit objectarray. It immediately retrieves the data stored in that wrapper and unarchives it, recreating the data model objects.

The -loadFromContents:ofType:error: method must return YES if it was successful, and NO if there were problems interpreting the document data. If the wrapper contained a things.data wrapper, and the data in that wrapper was successfully converted back into an array of MyWhatsit objects, the method assumes the document is valid and returns YES.


This, almost, concludes the work needed to save, and later open, your new document. Theres one glaring hole: the array of MyWhatsit objects cant be archived! Lets fix that now.

OTHER STORAGE ALTERNATIVES

The last two document  storage solutions  available to you are Core Data and DIY (Do It Yourself). DIY is one I rarely find appealing. It should be your last resort, because you’ll be forced to deal with all of the tasks, bothmundane and exceptional, that UIDocument normally handles for you. My advice is work very hard to make one of the first three solutions work. If that fails, you can perform your own document storage handling. Consult the“Advanced Overrides” section of UIDocuments documentation.

One of the most interesting  document solutions is Core Data. iOS includes a fast and efficient relational database engine (SQLite) with language-level  support. Core Data is far beyond the scope of this book, but its anincredibly powerful tool
if your apps data fits better into a database than a text file. (Its a shame I don’t have enough pages, because MyStuff would have made a perfect Core Data app.)

One of the huge advantages of using Core Data is that document management  is essentially done for you. You don’t have to do much beyond using the UIManagedDocument class (a subclass of UIDocument). Many of thefeatures in this chapter that you will write code to support—incremental document updating, lazy document loading, archiving and
unarchiving of your data model objects, background document loading and saving, cloud synchronization, and so on—are all provided “for free” by UIManagedDocument.

The prerequisite, of course, is that you must first base your app on Core Data. Your data model objects must be NSManagedObjects, you must design a schema for your database, and you have to understand
the ins andouts of Object-Oriented Database (OODB) technology. But beyond that (!), its childs play.

Archiving Objects

In Chapter 18 you learned all about serialization. Serialization turns a graph of property list objects into a
stream of bytes (either in XML or binary format) that can be stored in files, exchanged with other processes,transmittedto other computer systems, and so on. On the receiving end, those bytes are turned back into
an equivalent set of property list objects, ready to be used.

Archiving is serializations big sisterArchiving serializes (the computer science term) a graph of objects thatall adopt the NSCoding protocol. This is a much larger set of objects than the property- list objects. Moreimportantly, you can adopt the NSCoding protocol in classes you develop. Your custom objects can then be archived right along with other objects. This is exactly what needs to happen to your MyWhatsit class.


Adopting NSCoding

The first step to archiving a graph of objects is to make sure that every object adopts the NSCoding protocol. If one doesnt, you either need to eliminate it from the graph or change it so it does. In MyWhatsit.h, change the@interface declaration so it adopts NSCoding (new code in bold):

@interface MyWhatsit  : NSObject  <NSCoding>

The NSCoding protocol requires a class to implement two methods: -initWithCoder: and
-encodeWithCoder:. The first “init” method reconstructs an object from data that was previously archived. The second creates the archive data from the existing object. Both of these processes work through an NSCoderobject. The NSCoder object does the work of serializing (encoding), and later deserializing (decoding), your objects properties.

The coder identifies each property value of your object using a key. Define those keys now by adding these declarations before the @implementation  section in your MyWhatsit.m file:

#define kNameCoderKey               @"name"
#define  kLocationCoderKey          @"location"

Now you can add the two required methods to the @implementation  section:

-  (id)initWithCoder:(NSCoder *)decoder
{
self = [super init]; if (self!=nil)
{
_name =          [decoder  decodeObjectForKey:kNameCoderKey];
_location = [decoder  decodeObjectForKey:kLocationCoderKey];
}
return self;
}

2All property list objects adopt NSCoding. Property list objects are, therefore, a subset of the archivable
objects.

The -initWithCoder: method follows the typical pattern for an “init” method. But instead of initializing the new objects properties with default values, or from parameters, it retrieves the previously archived values from the coderobject. In this case, both of the values are (NSString) objects. Coder objects can also directly encode integer, floating-point, Boolean, and other C primitive types. UIKit adds categories to NSCoder to encode point, rectangle, size, affine transforms, and similar data structures.

-  (void)encodeWithCoder:(NSCoder *)coder
{
[coder  encodeObject:_name                   forKey:kNameCoderKey]; [coder encodeObject:_location                                                                forKey:kLocationCoderKey];
}

Translation in the other direction is provided by your -encodeWithCoder: method.
This method preserves the current values of its persistent properties in the coder object.
Your MyWhatsit objects are now ready to participate in the archiving process.

SUBCLASSING AN <NSCODING>  CLASS

When you subclass class that already adopts NSCoding, you do things a little differently. Your -initWithCoder:
method will look like this:

-  (id)initWithCoder:(NSCoder *)decoder
{
self = [super initWithCoder:decoder]; if (self!=nil)
{
... perform   subclass  decoding here   ...
}
return self;
}

And your -encodeWithCoder: method should look like this:

-  (void)encodeWithCoder:(NSCoder *)coder
{
[super   encodeWithCoder:coder];
... perform   subclass  encoding here   ...
}

Your super class already encodes and decodes its properties. Your subclass must allow the superclass
to do that, and then encode and decode any additional properties defined in the subclass.

Archiving and Unarchiving Objects

When you want to flatten your objects into bytes, use code like this:

NSData *data = [NSKeyedArchiver  archivedDataWithRootObject:things];

The NSKeyedArchiver class is the archiving engine. It creates an NSCoder object and then proceeds to send the root object (things) an -encodeWithCoder: message.
That object is responsible for preserving its content in the coder object. Most likely, it will send the coder object -encodeObject:forKey: messages for the objects it refers to. Those objects receive an
-encodeWithCoder: message, and the process repeats until all of the objects have been encoded.
The only limitation is that every object must adopt NSCoding.

When you want your objects back again, you use the NSKeyedUnarchiver class, like this:

things = [NSKeyedUnarchiver   unarchiveObjectWithData:data];

During the encoding process, the coder recorded the class of each object. The decoder then uses that information to create new objects and sends each one an -initWithCoder: message.
The resulting object is the sameclass, and has the same property values, as the originally encoded object.

The Archiving Serialization Smackdown

Now that you’ve added both serialization (property lists) and archiving (NSCoding objects) to your repertoire, I’d like to take a moment to compare and contrast the two. Table 19-1 summarizes their major features.

 Table 19-1. Serialization vs. Archiving

Feature                           Serialization                                                                  Archiving

Object Graph                Property list objects only                                           Objects that adopt NSCoding

XML?                             Yes                                                                                 No

Portability                      Cocoa or Cocoa Touch apps, or any system that can parse the XML version

Only another process that includes all of the original classes


Editors?                         Yes                                                                                 No

Property lists are much more limited in what you can store in them, but make up for that in the number of
ways you can store, share, and edit them. Use property lists when your values need to be understood by otherprocesses, particularly processes that dont include your custom classes. An example is the settings bundle you created in Chapter 18. The Settings app will never include any of your custom Objective-C classes, yet you were able to define, exchange, and incorporated those settings into your app using property lists. Property lists are the “universal”language of values.

Archiving, by contrast, can encode a vast number of classes and you can add your own classes to that roster by adopting the NSCoding protocol. Everything you create in Interface Builder is encoded using keyed archiving.When you load an Interface Builder file in your application, NSKeyedUnarchiver is busy translating that file back into the objects you defined. Archiving is extremely flexible, and has long reach, which is why it's the technology of choice for storing your data model objects in a document.

So why dont we use archiving for everything? When unarchiving, every class recorded in the archive must exist. So forget about trying to read your MyStuff document using another app or program that doesnt
include yourMyWhatsit code—you cant do it. Archives are, for the most part, opaque. There are no general purposes editors for archives, like there are for property lists. There is no facility for turning archive data into XML documents.Interface Builder is the closest thing to an archive editor there is, but its a one-way trip; you edit your interface and compile it into an archive, but you cant open a compiled archive file and edit it.

Serialization, Meet Archiving

Now that you have a feel for the benefits and limitations of archiving and serialization, I’m going to show you a really handy trick for combining the two. (You may have already figured this out, but
you could at least pretend to be surprised.) NSData is a property list object. The result of archiving a graph of NSCoding objects is an NSData object. Do you see where this is going?

By first archiving your objects into an NSData object, you can store non-property list objects in a property list, like user defaults! Your code would look like this:

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults] NSData *data = [NSKeyedArchiver  archivedDataWithRootObject:dataModel]; [userDefaults  setObject:data  forKey:@"data_model"];

What you’ve done is archive your data model objects into an NSData object, which can be stored in a property list. To retrieve them again, reverse the process:

NSData *modelData  = [userDefaults  objectForKey:@"data_model"]; dataModel  = [NSKeyedUnarchiver   unarchiveObjectWithData:modelData];

The disadvantages of this technique are the same ones that apply to archiving in general. The process retrieving the objects must to be able to unarchive them. Also, any editors or other programs
that examine your propertyvalues will just see a blob of data. Contrast this to the technique you used in Pigeon to convert the MKAnnotation object into a dictionary. Those property list values (the locations
name, longitude, and latitude) are easilyinterpreted, and could even be edited, by another program.

I think thats enough about archiving and property lists. Its time to get back to the business of
getting MyStuff documentified.

Document, Your Data Model

Where were we? Oh, thats right, you created a UIDocument class and wrote all of the code needed to
translate your data model objects into document data and back again. The next step is to make your
MSThingsDocumentobject the data model for MSMasterViewController. Your current MSMasterViewController
is using an NSArray as its data model object. The array object provides a number of methods that the view controller is using. This includes counting the number of objects in the array, along with adding, removing, and locating objects in the array. UIDocument doesnt have any of these methods—because its not a data model. Turn it into a data model by replicating the functions the view controller needs. SelectMSThingsDocument.h and add these methods to its @interface section (new code in bold):

@class  MyWhatsit;

@interface MSThingsDocument  : UIDocument

(NSURL*)documentURL;
(MSThingsDocument*)documentAtURL:(NSURL*)url;

@property (readonly) NSUInteger whatsitCount;
-  (MyWhatsit*)whatsitAtIndex:(NSUInteger)index;
-  (NSUInteger)indexOfWhatsit:(MyWhatsit*)object;
-  (void)removeWhatsitAtIndex:(NSUInteger)index;
-  (MyWhatsit*)anotherWhatsit;

@end



Switch to the MSThingsDocument.m implementation file. Add another #import directive to get the
MyWhatsit class definition:

#import  "MyWhatsit.h"

Now add the code for the data model methods to the @implementation  section:

-  (NSUInteger)whatsitCount
{
return things.count;
}

-  (MyWhatsit*)whatsitAtIndex:(NSUInteger)index
{
return things[index];
}

-  (NSUInteger)indexOfWhatsit:(MyWhatsit*)object
{
return [things indexOfObject:object];
}

-  (void)removeWhatsitAtIndex:(NSUInteger)index
{
[things  removeObjectAtIndex:index];
}

-  (MyWhatsit*)anotherWhatsit
{
MyWhatsit  *newItem  = [MyWhatsit  new];
newItem.name  = [NSString  stringWithFormat:@"My Item  %u",self.whatsitCount+1]; [things  addObject:newItem];
return  newItem;
}

The purpose of these methods should be obvious. The view controller will now send these messages to your document object to count the number of items, get the item at a specific index,
discover the index of an existingitem, remove an item, or create a new item. The next step is to make these changes in the view controller. Select your MSMasterViewController.h file and add this
#import statement:

#import "MSThingsDocument.h"

Select your MSMasterViewController.m file in the project navigator. Find the private @interface
section and replace the old things array with your document object (new code in bold):

@interface  MSMasterViewController ()  {
MSThingsDocument  *document;
}
Your document object is now your data model. Now you need to go through your view controller code and replace every reference to the old things array with equivalent code for your document.

You’ll also be removing the code that created the fake items for testing and replacing that with code to
load your data model from the document. Start in the -awakeFromNib method.
Delete the code that filled the things array with items. You dont need that anymore, because MyStuff will
fill the data model from the contents of the document. Find the  viewDidLoad method and add this statement to the end of the method:

document  = [MSThingsDocument documentAtURL:[MSThingsDocument  documentURL]];

You should remember these methods from the beginning of the chapter. This statement requests a new MSThingsDocument object with the contents of the document at [MSThingsDocument documentURL], which is the fixeddocument in your apps Documents folder. Thats it. Your document object is created, and loaded, in a single statement.

The rest of the work is mostly replacing code that used things with code that will use document. Find the -insertNewObject: method and change it so it reads (modified code in bold):

-  (void)insertNewObject:(id)sender
{
MyWhatsit  *newItem  = [document anotherWhatsit]NSUInteger  newIndex = [document indexOfWhatsit:newItem]; NSIndexPath  *indexPath = [NSIndexPath   indexPathForRow:newIndex
inSection:0]; [self.tableView  insertRowsAtIndexPaths:@[indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
}

This is the biggest change. The document object now takes care of creating a new MyWhatsit object—you’ll understand why when you work on the code for MyWhatsit images. The code is also modified to ask document wherein the array it added the new item, rather than assuming that it inserted at the beginning of the array. This is a smart change, because the -anotherWhatsit method actually inserts new items at the end of the array. And if you everaltered that 
again, this code would still work.

The rest of the changes are so mundane that I’ve summarized them below. (Hint: follow the trail of
compiler errors and replace the things statements with equivalent document statements.)

n     In -tableView:numberOfRowsInSection: things.count becomes document.whatsitCount

n     In -tableView:cellForRowAtIndexPath:, -tableView:didSelectRowAtIndexPath:,
     and –prepareForSeque: things[indexPath.row] becomes [document  whatsitAtIndex:indexPath.row]

n     In -tableView:commitEditingStyle:forRowAtIndexPath:[things removeObjectAtIndex:indexPath.row]
      becomes [document removeWhatsitAtIndex:indexPath.row]

n     In -whatsitDidChangeNotification:[things indexOfObject:notification.object] becomes [document indexOfWhatsit:notification.object] 

Your MSThingsDocument object is now your apps data model. This is an important step. Its not important that you combined the document and data model into a single object, but it is important that you’ve encapsulated allof the changes to the data model—counting, getting, removing, and creating items—behind your own methods, rather than simply using NSArray methods. You’ll see why shortly.

You might think that you’ve written enough code that your app would be able to store its MyWhatsit objects (at least the name and location bits) in your document and retrieve them again. But there are still a few small piecesmissing.


Tracking Changes

One thing you havent written is any code to save your document. You’ve written code to convert your data model objects into something that can be saved, but you’ve never asked the UIDocument object to use it.

And you wont. At least, thats not the ideal technique. UIDocument embraces the auto-save document model, where the users document is periodically saved to persistent storage while they work, and again automatically before your appquits. This is the preferred document-saving model for iOS apps. For auto-saving to work, your code must notify the document that changes have been made. UIDocument then schedules and performs the saving of the new data in the background.
There are two ways to communicatechanges to your document: send it an -updateChangeCount: message or use the documents NSUndoManager object. As you register changes with the NSUndoManager, it will automatically notify its document object of thosechanges.

You’re not going to embrace NSUndoManager for this app—although its a great feature to consider, and not at all difficult to use. Consequently, you’ll need to send your document object an
-updateChangeCount: message whenever something changes. UIDocument will take it from there.

So when does your data model change? One obvious place is whenever items are added or removed. Select the MSThingsDocument.m implementation file. Locate the -removeWhatistAtIndex: and -anotherWhatsit methods. Atthe end of -removeWhatistAtIndex:,
and again just before the return statement in -anotherWhatsit, add the following statement:

[self updateChangeCount:UIDocumentChangeDone];

This message tells the document object that its content was changed. There are other kinds of
changes (changes due to an undo or redo action, for example), but unless you’ve created your own
undo manager, this is the only constant you need to send.

The other place that the document changes is when the user edits an individual item. You already solved that problem way back in Chapter 4! Whenever a MyWhatsit object is edited, your object posts a MyWhatsitDidChangenotification. All your document needs to do is observe that notification.

Back at the top of MSThingsDocument.m, declare a new method to the private @interface MSThingsDocument  () section:

-  (void)whatsitDidChange:(NSNotification*)notification;

In your +documentAtURL: method, observe this notification by adding this code immediately before the return statement:

NSNotificationCenter *center  = [NSNotificationCenter defaultCenter]; [center  addObserver:document
selector:@selector(whatsitDidChange:) name:kWhatsitDidChangeNotification
object:nil];

For objects, like UIDocument, that can be destroyed before the app quits, remember that you must
remove the object from the notification center before it is destroyed. 
Do this by adding a -dealloc method to the class:

-  (void)dealloc
{
[[NSNotificationCenter defaultCenter]  removeObserver:self];
}

Finally, add the new notification handler method:

-  (void)whatsitDidChange:(NSNotification *)notification
{
if ([self indexOfWhatsit:notification.object]!=NSNotFound) [self updateChangeCount:UIDocumentChangeDone];
}

Its only purpose is to notify the document that a MyWhatsit object in this document has changed,
and thats what it does.

No comments:

Post a Comment