1. Trang chủ
  2. » Công Nghệ Thông Tin

Tài liệu Lập trình iPhone part 11 ppt

32 263 0
Tài liệu được quét OCR, nội dung có thể không chính xác
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề iPhone Programming Part 11
Tác giả Letxiv, Ewmg, Exe
Trường học University of Science and Technology of Hanoi
Chuyên ngành iPhone Programming
Thể loại Giáo trình
Năm xuất bản 2023
Thành phố Hà Nội
Định dạng
Số trang 32
Dung lượng 420,34 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

The downside of using a single file is that you have to load all of your application's data into memory, and you have to write all of it to the file system for even the smallest changes.

Trang 1

Basic Data

Persistence

o far, we’ve focused on the controller and view aspects of the Model-View-

Controller paradigm Although several of our applications have read data

out of the application bundle, none of our applications have saved their data

to any form of persistent storage, persistent storage being any form of non-

volatile storage that survives a restart of the computer or device So far, every

sample application either did not store data or used volatile or nonpersistent

storage Every time one of our sample applications launched, it appeared with

exactly the same data it had the first time you launched it

This approach has worked for us up to this point But in the real world, your

application needs to persist data so that when users make changes, those

changes are stored and are there when they launch the program again

A number of different mechanisms are available for persisting data on the

iPhone If you've programmed in Cocoa for Mac OS X, you've likely used some

or all of these techniques

In this chapter, we're going to look at three different mechanisms for persist-

ing data to the iPhone's file system We're going to look at using property lists,

object archives (or archiving), and the iPhone’s embedded relational database,

called SQLite3 We will write example applications that use all three

Trang 2

OTE

Property lists, object archives, and SQLite3 are not the only ways you can persist data on an iPhone They are just the three most common and easiest You always have the option of using traditional C 1/0 calls

like fopen () to read and write data You can also use Cocoa’s low-level file management tools In almost

every case, doing so will result in a lot more coding effort and is rarely necessary, but those tools are there

if you need them

Your Application’s Sandbox

All three of this chapter's data-persistence mechanisms share an important common element, your application’s /Documents folder Every application gets its own /Documents folder and (with the exception of Apple applications, such as Settings), applications are restricted to read- ing only what's in their own /Documents directory

To give you some context, let’s take a look at what an application looks like on the iPhone Open a Finder window, and navigate to your home directory Then, within that, drill down into Library/Application Support/iPhoneSimulator/User/ At this point, you should see five subfolders, one of which is named Applications (see Figure 11-1)

The sb files contain settings that the simulator uses to launch the program that shares the same

name You should never need to touch those If you open one of the application directories,

however, you should see something that looks a little more familiar In there, you'll find one of the

iPhone applications you've built, along with three support folders: Documents, Library, and tmp Your application stores its data in Documents, with the exception of NSUserDefau1ts-based pref-

erence settings, which get stored in the Library/Preferences folder The tmp directory offers a place

Trang 3

where your application can store temporary files Files written into /tmp will not be backed up by iTunes when your iPhone syncs, but your application does need to take responsibility for deleting the files in tmp once they are no longer needed, to avoid filling up the file system

Getting the Documents Directory

Since our application is in a folder with a seemingly random name, how do we retrieve the full path to the Documents directory so that we can read and write our files? It’s actually

quite easy There is a C function called NSSearchPathForDirectoriesInDomain that will

find various directories for you This is a Foundation function, so it is shared with Cocoa for

Mac OS X Many of its available options are designed for OS X and won't return any values

on the iPhone, because those locations don’t exist on the iPhone (the Downloads folder) or your application doesn’t have rights to access the location due to the iPhone's sandboxing mechanism

Here’s some code to retrieve the path to the documents directory:

NSArray *paths = NSSearchPathForDi rectoriesInDomains(NSDocumentDi rectory, NSUserDomainMask, YES);

NSString *documentsDirectory = [paths objectAtIndex:0];

The constant NSDocumentDi rectory says we are looking for the path to the Documents

directory The second constant, NSUserDomainMask, indicates that we want to restrict our

search to our application's sandbox In Mac OS X, this same constant is used to indicate that

we want the function to look in the user’s home directory, which explains its somewhat odd

name

Though an array of matching paths is returned, we can count on our Documents directory residing at index 0 in the array Why? We know that only one directory meets the criteria

we've specified since each application has only one Documents directory We can create

a filename, for reading or writing purposes, by appending another string onto the end of the path we just retrieved We'll use an NSString method designed for just that purpose called stringByAppendingPathComponent :, like so:

NSString *filename = [documentsDirectory

stringByAppendingPathComponent:@"theFile.txt"];

After this call, fi ]ename would contain the full path to a file called theFile.txt in our applica- tion’s Documents directory, and we can use fi lename to create, read, and write from that file

Getting the tmp Directory

Getting a reference to your application’s temporary directory is even easier than getting a ref- erence to the Documents directory The Foundation function called NSTemporaryDi rectoryQ will return a string containing the full path to your application's temporary directory To create

Trang 4

a filename for a file that will get stored in the temporary directory, we first find the temporary directory:

NSString *tempPath = NSTemporaryDirectoryQ ;

Then, we create a path to a file in that directory by appending a filename to that path, like this:

NSString *tempFile = [tempPath

stringByAppendingPathComponent :@"tempFi le txt" ];

File Saving Strategies

As a reminder, in this chapter, we're going to look at three different approaches to data per- sistence All three approaches make use of your iPhone’s file system

In the case of SQLite3, you'll create a single SQLite3 database file and let SQLite3 worry about storing and retrieving your data With the other two persistence mechanisms, prop- erty lists and archiving, you need to put some thought into whether you are going to store

your data in a single file or in multiple files

Single-File Persistence

Using a single file is the easiest approach, and with many applications, it is a perfectly acceptable one You start off by creating a root object, usually an NSArray or NSDictionary, though your root object can also be based on a custom class Next, you populate your root object with your program data Whenever you need to save, your code rewrites the entire contents of that root object to a single file When your application launches, it reads the entire contents of that file into memory, and when it quits, it writes out the entire contents This is the approach we'll use in this chapter

The downside of using a single file is that you have to load all of your application's data into memory, and you have to write all of it to the file system for even the smallest changes If

your application isn’t likely to manage more than a few megabytes of data, this approach is probably fine, and its simplicity will certainly make your life easier

Multiple-File Persistence

The multiple file approach is definitely more complicated As an example, you might write

an e-mail application that stored each e-mail message in its own file There are obvious advantages to this method It allows the application to load only data that the user has

requested (another form of lazy loading), and when the user makes a change, only the files

that changed have to be saved This method also gives you the opportunity to free up mem-

ory when you receive a low-memory notification, since any memory that is being used to

Trang 5

store data that the user is not currently looking at can be flushed and simply reloaded from the file system the next time it’s needed The downside of multiple-file persistence is that

it adds a fair amount of complexity to your application For now, we'll stick with single-file persistence

Persisting Application Data

Let's get into the specifics of each of our three persistence methods: property lists, object archives, and SQLite3 We'll explore each of these in turn, and build an application that uses each mechanism to save some data to the iPhone's file system We'll start with property lists

Property List Serialization

Several of our applications have made use of property lists, most recently when we used

a property list to specify our application preferences Property lists are convenient, because they can be edited manually using Xcode or the Property List Editor application, and both NSDictionary and NSArray instances can be written to and created from property lists as long as the dictionary or array contains only specific serializable objects A serialized object has been converted into a stream of bytes so it can be stored in a file or transferred over a net- work Although any object can be made serializable, only certain objects can be placed into

a collection class, such as an NSDictionary or NSArray, and then stored to a property list using the collection classes’ writeToFi le: atomically: method The Objective-C classes that can be serialized this way follow:

Trang 6

If you can build your data model from just these objects, you can use property lists to easily save and load your data In fact, we’ve used this mechanism in many of the sample applica- tions to provide you with sample data

If you're going to use property lists to persist your application data, you'll use either an NSArray

or an NSDictionary to hold the data that needs to be persisted Assuming that all of the objects

that you put into the NSArray or NSDictionary are serializable objects, you can write a property list by calling the wri teToFi le:atomically: method on the dictionary or array instance, like so:

[myArray writeToFi le: @"/some/file/location/output.plist" atomically:YES];

OTE

In case you were wondering, the atomi cal ly parameter tells the method to write the data to an

auxiliary file, not to the specified location Once it has successfully written the file, it will then copy that auxiliary file to the location specified by the first parameter This is a safer way to write a file, because if the application crashes during the save, the existing file, if there was one, will not be corrupted It adds

a tiny bit of overhead, but in most situations, it’s worth the cost

Not being able to serialize these objects also means that you can't easily create derived or calculated properties (for example, a property that is the sum of two other properties), and some of your code that really should be contained in model classes has to be moved to your controller classes Again, these restrictions are OK for simple data models and simple applli- cations Most of the time, however, your application will be much easier to maintain if you create dedicated model classes

However, simple property lists can still be useful in complex applications They are a great way to include data in your application For example, when your application includes a picker, often the best way to include the list of items to go in your picker is to create a property list file and include it in your project’s Resources folder, which will cause it to get compiled into your application

Let's a build a simple application that uses property lists to store its data

Trang 7

The Persistence Application

We're going to build a program that lets you enter data into four text fields, saves those fields to a property list file when the application quits, and then reloads the data back from that property list file the next time the application launches (see Figure 11-2)

OTE

In this chapter’s applications, we won't be taking the time to set up all the user interface niceties that we have in the past Tapping the return key, for example, will neither dismiss the keyboard nor take you to the next field If you want to add that polish to the application, doing so would be good practice, but it’s

not really material to this chapter's topic, so we won't be walking you through it

Creating the Persistence Project

In Xcode, create a new project using the view-based application template, and save the proj- ect with the name Persistence This project contains all the files that we'll need to build our

application, so we can dive right into things In a minute, we're going to build a view with

four text fields Let’s create the outlets we need before we go to Interface Builder Expand the Classes folder Then, single-click the PersistenceViewController.h file, and make the follow- ing changes:

#import <UIKit/UIKit.h>

#define kFilename @"data.plist"

@interface PersistenceViewController : UIViewController {

IBOutlet UITextField *fieldl1;

IBOutlet UITextField *field2;

IBOutlet UITextField *field3;

IBOutlet UITextField *field4;

}

@property (nonatomic, retain) UITextField *field1;

@property (Cnonatomic, retain) UITextField *field2;

@property (nonatomic, retain) UITextField *field3;

@property (nonatomic, retain) UITextField *field4;

- (NSString *)dataFilePath;

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

@end

Trang 8

In addition to defining four text field outlets, we've also

defined a constant for the filename we're going to use, as = well as two additional methods One method, dataFilePath,

will create and return the full pathname to our data file by p ` concatenating kFi lename onto the path for the Documents

Line 2: | ou tathors brought forth on thes

directory The other method, applicationwillTerminate:, tive 3: {coun view nama oomanes

which we'll discuss in a minute, will get called when our appli- Ling 4: |'n User, end otoeted to Ol

cation quits and will save data to the property list file

Designing the Persistence Application View

Once Interface Builder comes up, the window called View

should open as well If it doesn’t, double-click the View icon to

open it Drag a Text Field from the library, and place it against Figure 11-2 The property

the top-right blue guide line Expand it to the left so that it list application

reaches about two-thirds of the way across the window, and

then press 361 to bring up the attributes inspector Uncheck the box labeled Clear When Edit-

Next, hold down the option key and drag the

Line 1:

text box downward, which will create a copy of

it Repeat this step two more times so that you Line 2:

have four text fields Now, drag four labels to the Line 3:

window, and use Figure 11-3 as a placement and

Line 4:

design guide Notice that we've placed the text

fields at the top of our view so that there is room

for the keyboard

Once you have all four text fields and labels placed,

control-drag from the File’s Owner icon to each of

the four text fields Connect the topmost text field

to the outlet called field1, the next one to field2, the

third to field3, and the bottom one to field4 When

you have all four text fields connected to outlets,

save, close PersistenceViewController.xib, and go

Figure 11-3 Designing the Persistence application's view

Trang 9

Editing the Persistence Classes

Single-click PersistenceViewController.m, and make the following changes, which we'll dis- cuss when you finish typing:

NSArray *paths = NSSearchPathForDirectoriesInDomains(

NSDocumentDirectory, NSUserDomainMask, YES);

NSString *documentsDirectory = [paths objectAtIndex:0];

return [documentsDirectory stringByAppendingPathComponent:kFilename] ;

(void) applicationWillTerminate: (NSNotification *)notification

NSMutableArray *array = [[NSMutableArray alloc] init];

[array addObject: fieldl.text];

[array addObject: field2.text];

[array addObject: field3.text];

[array addObject: field4.text];

[array writeToFile:[self dataFilePath] atomically: YES];

[array release];

}

#pragma mark -

- (void)viewDidLoad {

NSString *filePath = [self dataFilePath];

if CCLENSFileManager defaultManager] fileExistsAtPath: filePath])

{

NSArray “array = [[NSArray alloc] initWithContentsOfFile:filePath]; fieldl.text = [array objectAtIndex:0];

field2.text = [array objectAtIndex:1];

field3.text [array objectAtIndex: 2];

field4.text = [array objectAtIndex: 3];

[array release];

UIApplication *app = [UIApplication sharedApplication];

[LINSNotificationCenter defaultCenter] addObserver:self

selector: @selector(appl]icationWwillTerminate: )

Trang 10

name : U[ApplicationWillTerminateNoti fication object:app];

[super viewDidLoad];

}

- (BOOL) shouldAutorotateToInterfaceOrientation:

(UIInterfaceOrientation)interfaceOrientation {

// Return YES for supported orientations

return CinterfaceOrientation == UIInterfaceOrientationPortrait);

}

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

// Releases the view if it doesn't have a superview

// Release anything that's not essential, such as cached data

NSArray *paths = NSSearchPathForDi rectoriesInDomains(

NSDocumentDirectory, NSUserDomainMask, YES);

NSString *documentsDirectory = [paths objectAtIndex:0];

return [documentsDirectory stringByAppendingPathComponent:kFilename] ;

}

The second new method is called app1i cationwillTerminate: Notice that it takes

a pointer to an NSNoti fication as an argument applicationWillTerminate: is a noti-

fication method, and all notifications take a single NSNoti fi cation instance as their

argument

Notifications are a lightweight mechanism that objects can use to communicate with each other Any object can define one or more notifications that it will publish to the application’s notification center, which is a singleton object that exists only to pass these notifications

Trang 11

between objects Notifications are usually indications that some event occurred, and deliv- ered objects that publish notifications include a list of notifications in their documentation For example, if you look at Figure 11-4, you can see that the UIApp lication class publishes

Figure 11-4 Ul/Application documentation lists

all the notifications that it publishes

The purpose of most notifications is usually pretty obvious from their names, but the documentation contains further information if you find one whose purpose is unclear Our application needs to save its data before the application quits, so we are interested in the notification called UIApp1icationWi 11TerminateNoti fication.In a minute, when we write our vi ewDi dLoad method, we will subscribe to that notification and tell the notifica- tion center to call this method:

- (void)applicationWillTerminate: (NSNotification *)notification

Trang 12

The method itself is fairly simple We create a mutable array, add the text from each of the four fields to the array, and then write the contents of that array out to a property list file That's all there is to saving our data using property lists

In the vi ewDi dLoad method, we do a few more things The first thing we do is check to see if

a data file already exists If there isn’t one, we don’t want to bother trying to load it If the file does exist, we instantiate an array with the contents of that file and then copy the objects from

that array to our four text fields Because arrays are ordered lists, by copying them in the same order as we saved them, we are always sure to get the right values in the right fields

- (void)viewDidLoad {

NSString *filePath = [self dataFilePath] ;

if CLINSFileManager defaultManager] fileExistsAtPath:filePath])

{

NSArray *array = [[NSArray alloc] initWithContentsOfFile:filePath]; field1.text [array obJectAtTndex:0];

field2.text [array obJectAtTndex: 1];

field3.text [array obJectAtTndex: 2];

field4.text [array obJectAtTndex: 3];

[array release];

After we load the data from the property list, we get a reference to our application instance and use that to subscribe to the UIApp1icationWil1TerminateNoti fication, using the default NSNoti fi cationCenter instance and a method called addObserver:selector:name:object:

We pass an observer of sel f, which means that our PersistenceViewControl ler is the object that needs to get notified For selector, we pass a selector to the app1icationWil1Terminate: method we wrote a minute ago, telling the notification center to call that method when the notification is published The third parameter, name: , is the name of the notification that we're interested in receiving, and the final parameter, object :, is the object we're interested in getting the notification from If we pass ni1 for the final parameter, we would then get notified any time any method posted the UIApp1icationWi1l1TerminateNoti fication

UIApplication *app = [UIApplication sharedApplication] ;

[LINSNotificationCenter defaultCenter] addObserver:self

selector:@selector(CapplicationwillTerminate: ) name :UITApplicationWillTerminateNoti fication object: app];

After subscribing to the notification, we just give our superclass a chance to respond to

vi ewDidLoad, and we're done

[super viewDidLoad] ;

Trang 13

That wasn't too bad, was it? When our main view is finished loading, we look for a property list file If it exists, we copy data from it into our text fields Next, we register to be notified when the application terminates When the application does terminate, we gather up the values from our four text fields, stick them in a mutable array, and write that mutable array out to a property list

Why don’t you compile and run the application? It should build and then launch in the

simulator Once it comes up, you should be able to type into any of the four text fields

When you've typed something in them, press the home button (the circular button with the

rounded square in it at the bottom of the simulator window) It’s very important that you press the home button If you just quit the simulator, that’s the equivalent of force quitting

your application, and you will never receive the notification that the application is terminat- ing, and your data will never get saved

Property list serialization is pretty cool and very easy to use, but it’s a little limiting, since only

a small selection of objects can be stored in property lists Let's look at a little more robust approach

Archiving Model Objects

In the last part of Chapter 9, when we built the Presidents data model object, you saw an example of the process of loading archived data using NSCoder In the Cocoa world, the

term “archiving” refers to another form of serialization, but it’s a more generic type that any

object can implement Any object specifically written to hold data (model objects) should

support archiving The technique of archiving model objects lets you easily write complex

objects to a file and then read them back in As long as every property you implement in your Class is either a scalar like int or float or else is an instance of a class that conforms to the NSCoding protocol, you can archive your objects completely Since most Foundation and

Cocoa Touch classes capable of storing data do conform to NSCoding, archiving is actually relatively easy to implement for most classes

Although not strictly required to make archiving work, another protocol should be imple-

mented along with NSCoding—the NSCopying protocol, which is a protocol that allows your object to be copied Being able to copy an object gives you a lot more flexibility when using data model objects For example, in the Presidents application in Chapter 9, instead of that complex code we had to write to store changes the user made so we could handle both the Cancel and Save buttons, we could have made a copy of the president object and stored the changes in that copy If the user tapped Save, we'd just copy the changed version over to replace the original version

Trang 14

Conforming to NSCoding

The NSCoding protocol declares two methods, both required One encodes your object into an archive; the other one creates a new object by decoding an archive Both methods are passed

an instance of NSCoder, which you work with very much like NSUserDefauTts from the previ-

ous chapter You can encode and decode both objects and scalars using key-value coding

A method to encode an object might look like this:

- (void) encodeWithCoder: (NSCoder *)encoder

{

[encoder encodeObject:foo forKey:kFooKey];

[encoder encodeObject:bar forKkey:kBarKey] ;

[encoder encodeInt:someInt forkKey:kSomeIntKey] ;

[encoder encodeFloat:someFloat forkey:kSomeFloatKey ]

}

To support archiving in our object, we have to encode each of our instance variables into encoder using the appropriate encoding method, so we need to implement a method that initializes an object from an NSCoder, allowing us to restore an object that was previously archived Implementing the initwithCoder: method is slightly more complex than

encodeWi thEncoder : If you are subclassing NSObject directly, or subclassing some other class that doesn’t conform to NSCoding, your method would look something like the following:

- Cid) initWithCoder: CNSCoder *)decoder

{

if Cself = [super init])

{

self.foo = [decoder decodeObjectForKey:kFookey] ;

self.bar = [decoder decodeObjectForKey:kBarKey] ;

self.someInt = [decoder decodeIntForkey:kSomeIntKey] ;

self.someFloat = [decoder decodeFTloatForKey : kAgeKey ] ;

}

return self;

}

The method initializes an object instance using [super init], and if that’s successful,

it sets its properties by decoding values from the passed-in instance of NSCoder When implementing NSCoding for a class with a superclass that also conforms to NSCoding, the initWithCoder: method needs to look slightly different Instead of calling init on super,

it has to call ini twithCoder:, like so:

- Cid) initWithCoder: CNSCoder *)encoder

Trang 15

self.bar = [decoder decodeObJjectForKey :kBarKey ] ;

self.someInt = [decoder decodeIntForkey:kSomeIntKey] ;

self.someFloat = [decoder decodeFTloatForKey : kAgeKey ] ;

return self;

And that’s basically it As long as you implement these two methods to encode and decode all of your object’s properties, then your object is archivable and can be written to and read from archives

Implementing NSCopying

As we mentioned a few minutes ago, conforming to NSCopying is a very good idea for

any data model objects as well NSCopying has one method, called copywithZone:, and

it allows objects to be copied Implementing NSCopying is very similar to implementing 1nï tWï thCoder : You just need to create a new instance of the same class and then set all

of that new instance’s properties to the same values as this objects properties Here’s what

a copyWithZone: method might look like:

- Cid) copyWithZone: (NSZone *)zone

{

MyClass *copy = [[[self class] allocWithZone: zone] init];

copy.foo = [self.foo copy];

copy.bar = [self.bar copy];

Archiving a Data Object

Creating an archive from an object or objects that conforms to NSCoding is relatively easy First, we create an instance of NSMutab1eData to hold the encoded data and then create an NSKeyedArchiver instance to archive objects into that NSMutabl eData instance:

NSMutableData *data = [[NSMutableData alloc] init];

NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]

Trang 16

Once we've encoded all the objects we want to include, we just tell the archiver we're done, write the NSMutab1eData instance to the file system, and do memory cleanup on our objects

[archiver finishEncoding];

BOOL success = [data writeToFile:@"/path/to/archive"” atomically: YES]; [archiver release];

[data release];

If anything went wrong while writing the file, success will be set to NO If success is YES, the

data was successfully written to the specified file Any objects created from this archive will

be exact copies of the objects that were last written into the file

Unarchiving a Data Object

To reconstitute objects from the archive, we go through a similar process We create an NSData instance from the archive file and create an NSKeyedUnarchiver to decode the data:

NSData *data = [[NSData alloc] initWithContentsOfFile:path] ;

NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]

The object returned by decodeObjectForkKey: is autoreleased, so if we need to keep it around, we

need to retain it Assigning it to a property declared with the retain keyword usually handles this for

us, but if you’re not assigning it to a property and need the object to stick around past the end of the cur-

rent event loop, then you need to retain it

Ngày đăng: 26/01/2014, 10:20