iPhone开发基础教程笔记(十一)--第十一章 基本数据持久性(属性列表、归档对象、SQLite3)
来源:互联网 发布:linux保存退出命令步骤 编辑:程序博客网 时间:2024/06/07 04:18
第十一章 基本数据持久性
本章将介绍将数据持久存储到iPhone的文件系统的三种不同的机制。并介绍如何使用属性列表、对象归档以及iPhone的嵌入式关系
数据库(称为SQLite3)。
说明:要将数据持久存储在iPhone上,并不是只有属性列表、对象归档以及SQLite3这几种方法,他们指示三种最常用且最简单的方
法。你可以始终选择使用传统的C I/O调用,如fopen(),读取和写入数据,也可以使用Cocoa的低级文件管理工具。但是需要更多代
码,且是不必要的。
11.1 应用程序的沙盒
每个应用程序都有自己的/Documents文件夹(但Apple应用程序例外,比如说Settings)并且应用程序仅能读取各自的/Documents目
录中的内容。
为了给你提供一个上下文,我们看一下iPhone上的应用程序。打开一个Finder窗口并导航到主目录。然后在该目录中,向下展开到
Library/Application Support/iPhoneSimulator/Users/。此时你应该看到5个文件夹,其中一个文件夹叫Applications。
显而易见,Applications文件夹就是iPhone存储其应用程序的文件夹。如果打开Applications文件夹,可以看到一系列文件夹和文
件,他们的名称是较长的字符串。这些名称都是由Xcode自动生成的。这些文件夹中的每个文件夹都包含一个应用程序及其支持的文
件夹。
.sb文件包含仿真器用于启动具备相同名称的程序的设置。你应该从不需要接触这些内容。但是如果打开其中一个应用程序目录,应
该会看到一些比较熟悉的内容。在这里,可以找到你构建的其中一个iPhone应用程序及其支持的3个文件夹:Documents、Library和
tmp。应用程序将其数据存储在Documents中(但基于NSUserDefaults的首选设置除外,他存储在Library/Preferences文件夹中)。
tmp目录供应用程序存储临时文件。当iPhone执行同步时,iTunes不会备份/tmp中的文件,但当不再需要这些文件时,应用程序需要
负责删除/tmp中的文件,以避免占用文件系统的空间。
11.1.1 获取Documents目录
使用NSSearchPathForDirectoriesInDomain的C函数来查找各种目录。他是Foundation函数,因此它可以与Coco for Mac OS X共享
。他的很多可用选项都是专门为OS X设计的,在iPhone上不会返回任何值。其原因在于,这些位置并不存在于iPhone(Downloads文
件)上,或者你的应用程序由于iPhone的沙盒机制而没有访问该位置的权限。
下面是一些代码:
NSArray *paths=NSSearchPathForDirectoriesInDomain(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *documentDirectory=[paths objectAtIndex:0];
常量NSDocumentDirectory表明我们正在查找Documents目录的路径。第二个常量NSUserDomainMask表明我们希望将搜索限制于我们
应用程序的沙盒。在Mac OS X中,此常量表示我们希望该函数查看用户的主目录,这解释了其名称古怪的原因。
尽管返回了一个匹配路径的数组,但是我们可以算出数组中位于索引0处的Documents目录。因为只有一个目录符合我们指定的条件
。即自己程序的Documents目录。我们可以通过在刚刚检索到的路径的结尾附加另一个字符串来创建一个文件名,用于执行或写入操
作。使用stringByAppendPathComponent:方法来达到此目的。如下:
NSString *filename=[documentsDirectory stringByAppendingPathComponent:@"theFile.txt"];
完成此调用之后,filename将包含theFile.txt文件的完整路径,该文件位于应用程序的Documents目录。我们可以使用filename来
创建读取和写入文件。
11.1.2 获取tmp目录
使用NSTemporaryDirectory() (Foundation函数)将返回一个字符串。例如:
NSString *tempPath=NSTemporaryDirectory();
NSString *tempFile=[tempPath stringByAppendingPathComponent:@"tempFile.txt"];
11.2 文件保存策略
如果使用SQLite3,你将创建一个SQLite3数据库文件,并让SQLite3负责存储和检索数据。使用其他两种持久性机制,即属性列表和
归档,你需要考虑是将数据存储在一个文件中还是多个文件中。
11.2.1 单个文件持久性
最简单,首先创建一个根对象,通常是NSArray或NSDictionary,然后填充根对象,需要保存时,代码会将根对象的全部内容重新写
入单个文件。需要读入时,也会将该文件的全部内容读入内存。
缺点在于,通存通取,内存消耗大,数据少时没问题。
11.2.2 多个文件持久性
假设你要编写一个电子邮件应用程序,该程序将每封电子邮件都存储在自己的文件中。该方法具有明显的优势,他允许应用程序仅
加载用户请求的数据(另一种形式的延迟加载),当用户进行更改时,只需保存更改的文件。此方法允许开发人员在收到内存不足
通知时释放内存,因为可以刷新用于存储用户当前未查看的数据的任何内存,并且只需在下次需要时从文件系统重新加载即可。
缺点是 复杂。
11.3 持久保存应用程序数据
属性列表序列化
属性列表非常方便,因为可以使用Xcode或Property List Editor应用程序手动编辑他们,并且只要字典或数组仅包含特定可序列化
的对象,就可以将NSDictionary和NSArray实例写入属性列表以及从属性列表创建他们。序列化对象以被转换为字节流,以便于存储
到文件中或通过网络进行传输。尽管可以让任何对象可序列化,但是只能将某些对象放置到某个集合类(如NSDictionary或NSArray
)中,然是使用该集合类的writeToFile:atomically:方法将他们存储到属性列表。可以按照该方法进行序列化的Obj-C类如下:
NSArray和NSMutableArray
NSDictionary和NSMutableDictionary
NSData和NSMutableData
NSString和NSMutableString
NSNumber
NSDate
如果可以只从这些对象构建数据模型,则可以使用属性列表轻松保存和加载数据。
如果你打算使用属性列表持久保存应用程序数据,则可以使用NSArray或NSDictionary容纳需要持久保存的数据。假设你放到
NSArray或NSDictionary中的所有对象都是可序列化对象,则可以通过对字典或数组实例调用writeToFile:atomically:方法来编写
属性列表:
[myArray writeToFile:@"/some/file/location/output.plist" atomically:YES];
说明:如果愿意,可以通过atomically参数让该方法将数据写入辅助文件,而不是写入指定位置。成功写入该文件之后,该辅助文
件将被复制到第一个参数指定的位置。这是更安全的写入文件的方法,因为如果应用程序在保存期间崩溃,则现有文件不会被破坏
。尽管这样增加了一点开销,但是多数情况下还是值得的。
属性列表方法的一个问题是无法将自定义对象序列化到属性列表中。也不能使用通过Cocoa Touch交付且未在之前的可序列化对象列
表中指定的其他类,这意味着无法直接使用NSURL、UIImage和UIColor等类。
不能序列化这些对象还意味着你无法轻松创建派生或计算的属性(例如,某两个属性之和的属性),并且必须将实际上包含在模型
类中的某些代码移动到控制器类。而且,这些限制也适用于简单数据模型和简单应用程序。但是多数情况下,如果创建了专用的模
型类,则应用程序更容易维护。
但是,在复杂的应用程序中,简单的属性列表仍然非常有用。他们是将数据保存在应用程序中的最佳方法。例如,当应用程序包含
一个选取器时,将项目列表包含到选取器的最佳方法是,创建一个属性列表文件并将其包含在项目的Resources文件夹中,这会将其
编译到应用程序中。
11.4 持久性应用程序
我们将构建一个程序,该程序允许你在4个文本字段中输入数据,应用程序退出时会将这些字段保存到属性列表文件,然后在下次启
动时从该属性列表文件中重新加载该数据。
11.4.1 创建持久性项目
在Xcode中,使用基于视图的应用程序模版创建一个新项目,并用名称Persistence保存该项目。
展开Classes文件夹,修改PersistenceViewController.h:
#define kFilename @"data.plist"
@interface PersistenceViewController:UIViewController {
IBOutlet UITextFiled *field1;
IBOutlet UITextFiled *field2;
IBOutlet UITextFiled *field3;
IBOutlet UITextFiled *field4;
}
@property (nonatomic,retain) UITextField *field1;
@property (nonatomic,retain) UITextField *field2;
@property (nonatomic,retain) UITextField *field3;
@property (nonatomic,retain) UITextField *field4;
- (NSString *)dataFilePath;
- (void)applicationWillTerminate:(NSNotification *)notification;
@end
11.4.2 设计持久性应用程序视图
在IB中设计视图,几个Label和文本字段。
11.4.3 编辑持久性类
单击PersistenceViewController.m:
- (NSString *)dataFilePath
{ NSArray *paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *documentDirectory=[paths objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:kFilename];
}
- (void)applicationWillTerminate:(NSNotfication *)notification
{ NSMutableArray *array=[[NSMutableArray alloc] init];
[array addObject:field1.text];
[array addObject:field2.text];
[array addObject:field3.text];
[array addObject:field4.text];
[array writeToFile:[self dataFilePath] atomically:YES];
[array release];
}
- (void)viewDidLoad {
NSString *filePath=[self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{ NSMutableArray *array=[[NSMutableArray alloc] initWithContentsOfFile:filePath];
field1.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];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillTerminate:)
name:UIApplicationWillTerminateNotfication
object:app];
[super viewDidLoad];
}
...
@end
第二个新方法称为applicationWillTerminate:。注意,他使用指向NSNotification的指针做为参数。applicationWillTerminate:
是通知方法,并且所有通知都使用一个NSNotification实例做为其参数。
通知是一种对象可以用于彼此通信的轻量级机制。任何对象都可以定义一个或多个通知,以发布到应用程序通知中心,她是一个单
独的对象,他存在的唯一目的是在对象之间传递这些通知。通常,通知是发生了某些事件的指示,被传递的发布通知的对象在其文
档中包含一个通知列表。有很多通知类,例如UIApplicationDidBecomeActiveNotification、
UIApplicationDidChangeInterfaceOrientationNotification等。
通常,大多数通知的作用都可以从其名称中明显看出,但是如果你发现某个通知的作用不明确,可以查看包含详细信息的文档。我
们的应用程序需要在退出之前保存其数据,因此我们对UIApplicationWillTerminateNotification通知更加感兴趣。随后在编写
viewDidLoad方法时,我们将订阅该通知并告知通知中心调用该方法。
viewDidLoad方法中,首先检查数据文件是否存在,如果存在则从其中加载内容。
然后获得对应用程序的引用,并使用该引用订阅UIApplicationWillTerminateNotification,使用默认的NSNotificationCenter实
例以及一个名为addObserver:selector:name:object:的方法。我们传递一个observer即self,这意味着
PersistenceViewController是需要通知的对象。对于selector,我们将一个selector传递给刚才编写的
applicationWillTerminate:方法,告知通知中心在发布该通知时调用该方法。第三个参数name:是我们对接收感兴趣的通知的名称
,最后一个参数是object:使我们对从众获取通知感兴趣的对象。如果我们为最后一个参数传递nil,则通知我们发布
UIApplicationWillTerminateNotification的时间和方法。
UIApplication *app=[UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillTerminate:)
name:UIApplicationWillTerminateNotfication
object:app];
为什么没有编译和运行应用程序呢?应该先构建,然后在仿真器中启动。实现之后,你应该能够在4个文本字段中的任何一个字段中
键入文本。在其中键入某些内容后,按Home按钮。按Home按钮非常重要,如果你刚刚退出仿真器,即等同于强制退出应用程序,则
从不会收到应用程序终止的通知,并且绝对不会保存你的数据。
属性列表序列化非常实用且易于使用,但是有一点限制,他只能将选择的一小部分对象存储在属性列表中。下面让我们看看比较强
大的方法。
11.4.4 对模型对象进行归档
在第9章的最后一个部分中,我们在构建Presidents数据模型对象之后,给出了一个使用NSCoder加载归档数据的过程示例。在Cocoa
世界中,术语“归档”是指另一种形式的序列化,但他是任何对象都可以实现的更常规的类型。专门编写用于保存数据(模型对象
)的任何对象都应该支持归档。使用对模型对象进行归档的技术可以轻松将复杂的对象写入文件,然后再从中读取他们。只要在类
中实现的每个属性都是标量(如int或float)或都是符合NSCoding协议的某个类的实例,你就可以对你的对象进行完整归档。由于
大多数支持存储数据的Foundation和Cocoa Touch类都符合NSCoding,因此,归档相对来说 比较容易实现。
尽管对使用归档没有严格要求,但是应该与NSCoding一起实现另一个协议--NSCopying协议,该协议允许复制对象。这使你在使用数
据模型对象时具备了更多的灵活性。
符合NSCoding
NSCoding协议声明了两个方法,这两个方法都是必须的。一个方法将对象编码到归档中;另一个方法通过对归档解码来创建一个新
对象。
对某个对象编码的方法可能类似于以下内容:
- (void)encodeWithCoder:(NSCoder *)encoder
{ [encoder encodeObject:foo forKey:kFooKey];
[encoder encodeObject:bar forKey:kBarKey];
[encoder encodeInt:someInt forKey:kSomeIntKey];
[encoder encodeFloat:someFloat forKey:kSomeFloatKey];
}
要在对象中支持归档,我们必须使用适当的编码方法将每个实例变量编码成encoder。因此,我们需要实现一个方法来初始化
NSCoder中的对象,以还原以前归档的对象。实现initWithCoder:方法比实现encodeWithEncoder:稍微复杂一些。如下:
- (id)initWithEncoder:(NSCoder *)decoder
{ if (self=[super init])
{self.foo=[decoder decodeObjectForKey:kFooKey];
self.bar=[decoder decodeObjectForKey:kBarKey];
self.someInt=[decoder decodeIntForKey:kSomeIntKey];
self.someFloat=[decoder decodeFloatKey:kAgeKey];
}
return self;
}
当为某个具有超类且符合NSCoding的类实现NSCoding时,initWithCoder:方法应稍有不同。它不再对super调用init,而是
initWithCoder:
基本就这些。
11.4.5 实现NSCopying
NSCopying有一个copyWithZone:方法,可用于复制对象。如下所示:
- (id)copyWithZone:(NSZone *)zone
{
MyClass *copy=[[[self class] allocWithZone:zone] init];
copy.foo=[self.foo copy];
copy.bar=[self.bar copy];
copy.someInt=self.someInt;
copy.someFloat=self.someFloat;
return copy;
}
注意我们并没有释放或者自动释放创建的新对象。复制的对象都被隐式保留,并且应该在名为copy的代码中释放或自动释放。
1,对数据对象进行归档
首先,创建一个NSMutableData实例,用于包含编码的数据,然后创建一个NSKeyedArchiver实例,用于将对象归档:
NSMutableData *data=[[NSMutableData alloc] init];
NSKeyedArchiver *archiver=[[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
创建这两个实例之后,我们使用键-值编码来对希望包含在归档中的所有对象进行归档,例如:
[archiver encodeObject:myObject forKey:@"keyValueString"];
对所有要包含的对象进行编码之后,我们只需告知归档程序已经完成了这些操作,将NSMutableData实例写入文件系统,并对对象进
行内存清理。
[archiver finishEncoding];
BOOL success=[data writeToFile:@"/path/to/archive" atomically:YES];
[archiver release];
[data release];
如果写入文件出现错误,success设置为NO。从该归档创建的任何对象都是过去写入该文件的对象的精确副本。
2,对数据对象取消归档
NSData *data=[[NSData alloc] initWithContentsOfFile:path];
NSKeyedUnarchiver *unarchiver=[[NSKeyedUnarchiver alloc] initForReadingWithData:data];
然后,使用之前用于对对象进行归档的相同密钥从解压程序中读取对象:
self.object=[unarchiver decodeObjectForKey:@"keyValueString"];
说明:自动释放由decodeObjectForKey:返回的对象,因此如果需要身边经常备用该对象,则需要保留该对象。将其分配给使用
retain关键字声明的属性通常可以为我们完成这个任务,但是如果你未将其分配给属性,并且需要让该对象位于当前事件循环的结
尾。则需要保留该对象。
最后,内存清理:
[unarchiver finishDecoding];
[unarchiver release];
[data release];
11.5 归档应用程序
改进Persistence有粗有细,让他使用归档而不是属性列表。
11.5.1 实现FourLines类
单击Classes文件夹,Cmd+N新建文件,现在NSObject subClass,命名为FourLines.m。
单击FourLines.h:
#define kField1Key @"Field1"
#define kField2Key @"Field2"
#define kField3Key @"Field3"
#define kField4Key @"Field4"
遵循 <NSCoding,NSCopying>协议,并声明 NSString *field1,field2,field3,field4;实例变量
单击FourLines.m:
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:field1 forKey:kField1Key];
[encoder encodeObject:field2 forKey:kField2Key];
[encoder encodeObject:field3 forKey:kField3Key];
[encoder encodeObject:field4 forKey:kField4Key];
}
- (id)initWithCoder:(NSCoder *)decoder
{
if (self=[super init])
{
self.field1=[decoder decodeObjectForKey:kField1Key];
self.field2=[decoder decodeObjectForKey:kField2Key];
self.field3=[decoder decodeObjectForKey:kField3Key];
self.field4=[decoder decodeObjectForKey:kField4Key];
}
return self;
}
- (id)copyWithZone:(NSZone *)zone
{
FourLines *copy=[[[self class] allocWithZone:zone] init];
copy.field1=[self.field1 copy];
copy.field2=[self.field2 copy];
copy.field3=[self.field3 copy];
copy.field4=[self.field4 copy];
return copy;
}
@end
11.5.2 实现PersistenceViewController类
单击PersistenceViewController.h:删除原来的#define,声明新的#define
#define kFileName @"archive"
#define kDataKey @"Data"
切换到PersistenceViewController.m:
- (void)applicationWillTerminate:(NSNotification *)notification
{
FourLines *fourLines=[[FourLines alloc] init];
fourLines.field1=field1.text;
fourLines.field2=field2.text;
fourLines.field3=field3.text;
fourLines.field4=field4.text;
NSMutableData *data=[[NSMutableData alloc] init];
NSKeyedArchiver *archiver=[[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:fourLines forKey:kDataKey];
[archiver finishEncoding];
[data writeToFile:[self dataFilePath] atomically:YES];
[fourLines release];
[archiver release];
[data release];
}
- (void)viewDidLoad {
NSString *filePath=[self dataFilePath];
if ([[NSFileManager dafaultManager] fileExistsAtPath:filePath])
{
NSData *data=[[NSMutableData alloc] initWithContentsOfFile:[self dataFilePath]];
NSKeyedUnarchiver *unarchiver=[[NSKeyedUnarchiver alloc] initForReadingWithData:data];
FourLines *fourLines=[unarchiver decodeObjectForKey:kDataKey];
[unarchiver finishDecoding];
field1.text=fourLines.field1;
field2.text=fourLines.field2;
field3.text=fourLines.field3;
field4.text=fourLines.field4;
[unarchiver release];
[data release];
}
UIApplication *app=[UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillTerminate:)
name:UIApplicationWillTerminateNotfication
object:app];
[super viewDidLoad];
}
11.6 使用iPhone的嵌入式SQLite3
创建和打开数据库 代码:
sqlite3 *database;
int result=sqlite3_open("/path/to/database/file",&database);//如果指定位置不存在数据库,则他会创建一个新的数据库
如果result==SQLITE_OK,则表示数据库已成功打开。此处你应该记住的一件事情就是,数据库文件的路径必须做为C字符串(而非
NSString)传递。SQLite3是采用可移植的C(而非Obj-C)编写的,他不知道什么是NSString。所幸,有一个NSString方法,该方法
从NSString实例生成C字符串:
char *cStringPath=[pathString UTF8String];
当你对SQLite3数据库执行完所有操作之后,通过调用以下内容来关闭数据库:
sqlite3_close(database);
使用sqlite3_exec:
char *errorMsg;
const char *createSQL="CREATE TABLE IF NOT EXISTS PEOPLE (ID INTEGER PRIMARY KEY AUTOINCREMENT,FIELD_DATA TEXT)";
int result=sqlite3_exec(database,createSQL,NULL,NULL,&errorMSG);
如果result==SQLITE_OK表示命令成功运行,否则errorMSG将包含对所发生问题的描述。
函数sqlite3_exec用于针对SQLite3运行任何不返回数据的命令。它用于执行更新、插入和删除操作。
从数据库中检索数据有点麻烦。必须首先通过向其输入SQL SELECT命令来准备该语法:
NSString *query=@"SELECT ID,FIELD_DATA FROM FIELDS ORDER BY ROW";
sqlite3_stmt *statement;
int result=(sqlite3_prepare_v2(database,[query UTF8String],-1,&statement,nil);
如果result==SQLITE_OK,则你的语句已成功准备,并且你可以开始单步调试结果集。下面是一个单步调试结果集并从数据库中检索
int和NSString的示例:
while (sqlite3_step(statement)==SQLITE_ROW) {
int rowNum=sqlite3_column_int(statement,0);
char *rowData=(char *)sqlite3_column_text(statement,1);
NSString *fieldValue=[[NSString alloc] initWithUTF8String:rowData];
[fieldValue release];
}
sqlite3_finalize(statement);
设置项目使用SQLite3
我们已经讲述了基本知识,下面介绍在实践中的工作原理。我们将再次改进Persistence应用程序,这次使用SQLite3来存储他的数
据。他将使用一个表并将字段值存储在该表中的4个不同的行中。我们将为每个行提供一个与其字段相对应的行号,例如,field1中
的值将存储在表中行号为1的行中。
通过一个过程API来访问SQLite3,该API提供对很多C函数调用的接口。要使用此API,我们需要将应用程序链接到一个名为
libsqlite3.dylib的动态库。在Mac OS X和iPhone上,该库位于/usr/lib中。
在Xcode中打开Persistence应用程序。在Groups&Files窗格中选择Frameworks。Project=>Add to Project...。然后导航到
/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator2.1.sdk/usr/lib,并找到名为
libsqlite3.dylib的文件。当系统弹出提示,确保取消选中标签为Copy item3 into destination group's folder (if needed)的
复选框。还要确保将Reference Type改为Relative to Current SDK。注意,该目录可能有多个以libsqlite3开头的其他条目。无比
选择libsqlite3.dylib。他是始终指向最新版本的SQLite3库的别名。
接下来,对PersistenceViewController.h进行以下更改:
#import "/usr/include/sqlite3.h"
#define kFileName @"data.sqlite3"
@interface PersistenceViewController:UIViewController {
...
sqlite3 *database;
}
切换到PersistenceViewController.m:
- (void)applicationWillTerminate:(NSNotification *)notification
{
for (int i=1;i<=4;i++)
{
NSString *fieldName=[[NSString alloc] initWithFormat:@"field%d",i];
UITextField *field=[self valueForKey:fieldName];
[fieldName release];
NSString *update=[[NSString alloc] initWithFormat:@"INSERT OR REPLACE INTO FIELDS (ROW,FIELD_DATA) VALUES (%d,'%@');",i,field.text];
char *errorMsg;
if (sqlite3_exec (database,[update UTF8String],NULL,NULL,&errorMsg) !=SQLITE_OK) {
NSAssert1(0,@"Error Updating tables:%s",errorMsg);
sqlite3_free(errorMsg);
}
}
sqlite3_close(database);
}
- (void)viewDidLoad {
if (sqlite3_open([[self dataFilePath] UTF8String],&database) != SQLITE_OK) {
sqlite3_close(database);
NSAssert(0,@"Failed to open database");
}
char *errorMsg;
NSString *createSQL=@"CREATE TABLE IF NOT EXISTS FIELDS (ROW INTEGER PRIMARY KEY,FIELD_DATA TEXT);";
if (sqlite3_exec (database,[createSQL UTF8String],NULL,NULL,&errorMsg) !=SQLITE_OK) {
sqlite3_close(database);
NSAssert1(0,@"Error creating table :%s",errorMsg);
}
NSString *query=@"SELECT ROW,FIELD_DATA FROM FIELDS ORDER BY ROW";
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(database,[query UTF8String],-1,&statement,nil)==SQLITE_OK) {
while (sqlite3_step(statement)==SQLITE_ROW) {
int row=sqlite3_column_int(statement,0);
char *rowData=(char *)sqlite3_column_text(statement,1);
NSString *fieldName=[[NSString alloc] initWithFormat:@"field%d",row];
NSString *fieldValue=[[NSString alloc] initWithUTF8String:rowData];
UITextField *field=[self valueForKey:fieldName];
field.text=fieldValue;
[fieldName release];
[fieldValue release];
}
sqlite3_finalize(statement);
}
...
}
解析:
在applicationWillTerminate:方法中使用了断言,NSAssert1,之所以使用断言,而不使用异常或手动检查错误,是因为这种情况只有在我们开发人员出错的情况下才会出现。使用此断言宏将有助于我们调试代码,并且可以脱离最终的应用程序。如果某个错误条件是用户正常情况下可能遇到的条件,则可能应该使用某些其他形式的错误检查。
本章将介绍将数据持久存储到iPhone的文件系统的三种不同的机制。并介绍如何使用属性列表、对象归档以及iPhone的嵌入式关系
数据库(称为SQLite3)。
说明:要将数据持久存储在iPhone上,并不是只有属性列表、对象归档以及SQLite3这几种方法,他们指示三种最常用且最简单的方
法。你可以始终选择使用传统的C I/O调用,如fopen(),读取和写入数据,也可以使用Cocoa的低级文件管理工具。但是需要更多代
码,且是不必要的。
11.1 应用程序的沙盒
每个应用程序都有自己的/Documents文件夹(但Apple应用程序例外,比如说Settings)并且应用程序仅能读取各自的/Documents目
录中的内容。
为了给你提供一个上下文,我们看一下iPhone上的应用程序。打开一个Finder窗口并导航到主目录。然后在该目录中,向下展开到
Library/Application Support/iPhoneSimulator/Users/。此时你应该看到5个文件夹,其中一个文件夹叫Applications。
显而易见,Applications文件夹就是iPhone存储其应用程序的文件夹。如果打开Applications文件夹,可以看到一系列文件夹和文
件,他们的名称是较长的字符串。这些名称都是由Xcode自动生成的。这些文件夹中的每个文件夹都包含一个应用程序及其支持的文
件夹。
.sb文件包含仿真器用于启动具备相同名称的程序的设置。你应该从不需要接触这些内容。但是如果打开其中一个应用程序目录,应
该会看到一些比较熟悉的内容。在这里,可以找到你构建的其中一个iPhone应用程序及其支持的3个文件夹:Documents、Library和
tmp。应用程序将其数据存储在Documents中(但基于NSUserDefaults的首选设置除外,他存储在Library/Preferences文件夹中)。
tmp目录供应用程序存储临时文件。当iPhone执行同步时,iTunes不会备份/tmp中的文件,但当不再需要这些文件时,应用程序需要
负责删除/tmp中的文件,以避免占用文件系统的空间。
11.1.1 获取Documents目录
使用NSSearchPathForDirectoriesInDomain的C函数来查找各种目录。他是Foundation函数,因此它可以与Coco for Mac OS X共享
。他的很多可用选项都是专门为OS X设计的,在iPhone上不会返回任何值。其原因在于,这些位置并不存在于iPhone(Downloads文
件)上,或者你的应用程序由于iPhone的沙盒机制而没有访问该位置的权限。
下面是一些代码:
NSArray *paths=NSSearchPathForDirectoriesInDomain(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *documentDirectory=[paths objectAtIndex:0];
常量NSDocumentDirectory表明我们正在查找Documents目录的路径。第二个常量NSUserDomainMask表明我们希望将搜索限制于我们
应用程序的沙盒。在Mac OS X中,此常量表示我们希望该函数查看用户的主目录,这解释了其名称古怪的原因。
尽管返回了一个匹配路径的数组,但是我们可以算出数组中位于索引0处的Documents目录。因为只有一个目录符合我们指定的条件
。即自己程序的Documents目录。我们可以通过在刚刚检索到的路径的结尾附加另一个字符串来创建一个文件名,用于执行或写入操
作。使用stringByAppendPathComponent:方法来达到此目的。如下:
NSString *filename=[documentsDirectory stringByAppendingPathComponent:@"theFile.txt"];
完成此调用之后,filename将包含theFile.txt文件的完整路径,该文件位于应用程序的Documents目录。我们可以使用filename来
创建读取和写入文件。
11.1.2 获取tmp目录
使用NSTemporaryDirectory() (Foundation函数)将返回一个字符串。例如:
NSString *tempPath=NSTemporaryDirectory();
NSString *tempFile=[tempPath stringByAppendingPathComponent:@"tempFile.txt"];
11.2 文件保存策略
如果使用SQLite3,你将创建一个SQLite3数据库文件,并让SQLite3负责存储和检索数据。使用其他两种持久性机制,即属性列表和
归档,你需要考虑是将数据存储在一个文件中还是多个文件中。
11.2.1 单个文件持久性
最简单,首先创建一个根对象,通常是NSArray或NSDictionary,然后填充根对象,需要保存时,代码会将根对象的全部内容重新写
入单个文件。需要读入时,也会将该文件的全部内容读入内存。
缺点在于,通存通取,内存消耗大,数据少时没问题。
11.2.2 多个文件持久性
假设你要编写一个电子邮件应用程序,该程序将每封电子邮件都存储在自己的文件中。该方法具有明显的优势,他允许应用程序仅
加载用户请求的数据(另一种形式的延迟加载),当用户进行更改时,只需保存更改的文件。此方法允许开发人员在收到内存不足
通知时释放内存,因为可以刷新用于存储用户当前未查看的数据的任何内存,并且只需在下次需要时从文件系统重新加载即可。
缺点是 复杂。
11.3 持久保存应用程序数据
属性列表序列化
属性列表非常方便,因为可以使用Xcode或Property List Editor应用程序手动编辑他们,并且只要字典或数组仅包含特定可序列化
的对象,就可以将NSDictionary和NSArray实例写入属性列表以及从属性列表创建他们。序列化对象以被转换为字节流,以便于存储
到文件中或通过网络进行传输。尽管可以让任何对象可序列化,但是只能将某些对象放置到某个集合类(如NSDictionary或NSArray
)中,然是使用该集合类的writeToFile:atomically:方法将他们存储到属性列表。可以按照该方法进行序列化的Obj-C类如下:
NSArray和NSMutableArray
NSDictionary和NSMutableDictionary
NSData和NSMutableData
NSString和NSMutableString
NSNumber
NSDate
如果可以只从这些对象构建数据模型,则可以使用属性列表轻松保存和加载数据。
如果你打算使用属性列表持久保存应用程序数据,则可以使用NSArray或NSDictionary容纳需要持久保存的数据。假设你放到
NSArray或NSDictionary中的所有对象都是可序列化对象,则可以通过对字典或数组实例调用writeToFile:atomically:方法来编写
属性列表:
[myArray writeToFile:@"/some/file/location/output.plist" atomically:YES];
说明:如果愿意,可以通过atomically参数让该方法将数据写入辅助文件,而不是写入指定位置。成功写入该文件之后,该辅助文
件将被复制到第一个参数指定的位置。这是更安全的写入文件的方法,因为如果应用程序在保存期间崩溃,则现有文件不会被破坏
。尽管这样增加了一点开销,但是多数情况下还是值得的。
属性列表方法的一个问题是无法将自定义对象序列化到属性列表中。也不能使用通过Cocoa Touch交付且未在之前的可序列化对象列
表中指定的其他类,这意味着无法直接使用NSURL、UIImage和UIColor等类。
不能序列化这些对象还意味着你无法轻松创建派生或计算的属性(例如,某两个属性之和的属性),并且必须将实际上包含在模型
类中的某些代码移动到控制器类。而且,这些限制也适用于简单数据模型和简单应用程序。但是多数情况下,如果创建了专用的模
型类,则应用程序更容易维护。
但是,在复杂的应用程序中,简单的属性列表仍然非常有用。他们是将数据保存在应用程序中的最佳方法。例如,当应用程序包含
一个选取器时,将项目列表包含到选取器的最佳方法是,创建一个属性列表文件并将其包含在项目的Resources文件夹中,这会将其
编译到应用程序中。
11.4 持久性应用程序
我们将构建一个程序,该程序允许你在4个文本字段中输入数据,应用程序退出时会将这些字段保存到属性列表文件,然后在下次启
动时从该属性列表文件中重新加载该数据。
11.4.1 创建持久性项目
在Xcode中,使用基于视图的应用程序模版创建一个新项目,并用名称Persistence保存该项目。
展开Classes文件夹,修改PersistenceViewController.h:
#define kFilename @"data.plist"
@interface PersistenceViewController:UIViewController {
IBOutlet UITextFiled *field1;
IBOutlet UITextFiled *field2;
IBOutlet UITextFiled *field3;
IBOutlet UITextFiled *field4;
}
@property (nonatomic,retain) UITextField *field1;
@property (nonatomic,retain) UITextField *field2;
@property (nonatomic,retain) UITextField *field3;
@property (nonatomic,retain) UITextField *field4;
- (NSString *)dataFilePath;
- (void)applicationWillTerminate:(NSNotification *)notification;
@end
11.4.2 设计持久性应用程序视图
在IB中设计视图,几个Label和文本字段。
11.4.3 编辑持久性类
单击PersistenceViewController.m:
- (NSString *)dataFilePath
{ NSArray *paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *documentDirectory=[paths objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:kFilename];
}
- (void)applicationWillTerminate:(NSNotfication *)notification
{ NSMutableArray *array=[[NSMutableArray alloc] init];
[array addObject:field1.text];
[array addObject:field2.text];
[array addObject:field3.text];
[array addObject:field4.text];
[array writeToFile:[self dataFilePath] atomically:YES];
[array release];
}
- (void)viewDidLoad {
NSString *filePath=[self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{ NSMutableArray *array=[[NSMutableArray alloc] initWithContentsOfFile:filePath];
field1.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];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillTerminate:)
name:UIApplicationWillTerminateNotfication
object:app];
[super viewDidLoad];
}
...
@end
第二个新方法称为applicationWillTerminate:。注意,他使用指向NSNotification的指针做为参数。applicationWillTerminate:
是通知方法,并且所有通知都使用一个NSNotification实例做为其参数。
通知是一种对象可以用于彼此通信的轻量级机制。任何对象都可以定义一个或多个通知,以发布到应用程序通知中心,她是一个单
独的对象,他存在的唯一目的是在对象之间传递这些通知。通常,通知是发生了某些事件的指示,被传递的发布通知的对象在其文
档中包含一个通知列表。有很多通知类,例如UIApplicationDidBecomeActiveNotification、
UIApplicationDidChangeInterfaceOrientationNotification等。
通常,大多数通知的作用都可以从其名称中明显看出,但是如果你发现某个通知的作用不明确,可以查看包含详细信息的文档。我
们的应用程序需要在退出之前保存其数据,因此我们对UIApplicationWillTerminateNotification通知更加感兴趣。随后在编写
viewDidLoad方法时,我们将订阅该通知并告知通知中心调用该方法。
viewDidLoad方法中,首先检查数据文件是否存在,如果存在则从其中加载内容。
然后获得对应用程序的引用,并使用该引用订阅UIApplicationWillTerminateNotification,使用默认的NSNotificationCenter实
例以及一个名为addObserver:selector:name:object:的方法。我们传递一个observer即self,这意味着
PersistenceViewController是需要通知的对象。对于selector,我们将一个selector传递给刚才编写的
applicationWillTerminate:方法,告知通知中心在发布该通知时调用该方法。第三个参数name:是我们对接收感兴趣的通知的名称
,最后一个参数是object:使我们对从众获取通知感兴趣的对象。如果我们为最后一个参数传递nil,则通知我们发布
UIApplicationWillTerminateNotification的时间和方法。
UIApplication *app=[UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillTerminate:)
name:UIApplicationWillTerminateNotfication
object:app];
为什么没有编译和运行应用程序呢?应该先构建,然后在仿真器中启动。实现之后,你应该能够在4个文本字段中的任何一个字段中
键入文本。在其中键入某些内容后,按Home按钮。按Home按钮非常重要,如果你刚刚退出仿真器,即等同于强制退出应用程序,则
从不会收到应用程序终止的通知,并且绝对不会保存你的数据。
属性列表序列化非常实用且易于使用,但是有一点限制,他只能将选择的一小部分对象存储在属性列表中。下面让我们看看比较强
大的方法。
11.4.4 对模型对象进行归档
在第9章的最后一个部分中,我们在构建Presidents数据模型对象之后,给出了一个使用NSCoder加载归档数据的过程示例。在Cocoa
世界中,术语“归档”是指另一种形式的序列化,但他是任何对象都可以实现的更常规的类型。专门编写用于保存数据(模型对象
)的任何对象都应该支持归档。使用对模型对象进行归档的技术可以轻松将复杂的对象写入文件,然后再从中读取他们。只要在类
中实现的每个属性都是标量(如int或float)或都是符合NSCoding协议的某个类的实例,你就可以对你的对象进行完整归档。由于
大多数支持存储数据的Foundation和Cocoa Touch类都符合NSCoding,因此,归档相对来说 比较容易实现。
尽管对使用归档没有严格要求,但是应该与NSCoding一起实现另一个协议--NSCopying协议,该协议允许复制对象。这使你在使用数
据模型对象时具备了更多的灵活性。
符合NSCoding
NSCoding协议声明了两个方法,这两个方法都是必须的。一个方法将对象编码到归档中;另一个方法通过对归档解码来创建一个新
对象。
对某个对象编码的方法可能类似于以下内容:
- (void)encodeWithCoder:(NSCoder *)encoder
{ [encoder encodeObject:foo forKey:kFooKey];
[encoder encodeObject:bar forKey:kBarKey];
[encoder encodeInt:someInt forKey:kSomeIntKey];
[encoder encodeFloat:someFloat forKey:kSomeFloatKey];
}
要在对象中支持归档,我们必须使用适当的编码方法将每个实例变量编码成encoder。因此,我们需要实现一个方法来初始化
NSCoder中的对象,以还原以前归档的对象。实现initWithCoder:方法比实现encodeWithEncoder:稍微复杂一些。如下:
- (id)initWithEncoder:(NSCoder *)decoder
{ if (self=[super init])
{self.foo=[decoder decodeObjectForKey:kFooKey];
self.bar=[decoder decodeObjectForKey:kBarKey];
self.someInt=[decoder decodeIntForKey:kSomeIntKey];
self.someFloat=[decoder decodeFloatKey:kAgeKey];
}
return self;
}
当为某个具有超类且符合NSCoding的类实现NSCoding时,initWithCoder:方法应稍有不同。它不再对super调用init,而是
initWithCoder:
基本就这些。
11.4.5 实现NSCopying
NSCopying有一个copyWithZone:方法,可用于复制对象。如下所示:
- (id)copyWithZone:(NSZone *)zone
{
MyClass *copy=[[[self class] allocWithZone:zone] init];
copy.foo=[self.foo copy];
copy.bar=[self.bar copy];
copy.someInt=self.someInt;
copy.someFloat=self.someFloat;
return copy;
}
注意我们并没有释放或者自动释放创建的新对象。复制的对象都被隐式保留,并且应该在名为copy的代码中释放或自动释放。
1,对数据对象进行归档
首先,创建一个NSMutableData实例,用于包含编码的数据,然后创建一个NSKeyedArchiver实例,用于将对象归档:
NSMutableData *data=[[NSMutableData alloc] init];
NSKeyedArchiver *archiver=[[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
创建这两个实例之后,我们使用键-值编码来对希望包含在归档中的所有对象进行归档,例如:
[archiver encodeObject:myObject forKey:@"keyValueString"];
对所有要包含的对象进行编码之后,我们只需告知归档程序已经完成了这些操作,将NSMutableData实例写入文件系统,并对对象进
行内存清理。
[archiver finishEncoding];
BOOL success=[data writeToFile:@"/path/to/archive" atomically:YES];
[archiver release];
[data release];
如果写入文件出现错误,success设置为NO。从该归档创建的任何对象都是过去写入该文件的对象的精确副本。
2,对数据对象取消归档
NSData *data=[[NSData alloc] initWithContentsOfFile:path];
NSKeyedUnarchiver *unarchiver=[[NSKeyedUnarchiver alloc] initForReadingWithData:data];
然后,使用之前用于对对象进行归档的相同密钥从解压程序中读取对象:
self.object=[unarchiver decodeObjectForKey:@"keyValueString"];
说明:自动释放由decodeObjectForKey:返回的对象,因此如果需要身边经常备用该对象,则需要保留该对象。将其分配给使用
retain关键字声明的属性通常可以为我们完成这个任务,但是如果你未将其分配给属性,并且需要让该对象位于当前事件循环的结
尾。则需要保留该对象。
最后,内存清理:
[unarchiver finishDecoding];
[unarchiver release];
[data release];
11.5 归档应用程序
改进Persistence有粗有细,让他使用归档而不是属性列表。
11.5.1 实现FourLines类
单击Classes文件夹,Cmd+N新建文件,现在NSObject subClass,命名为FourLines.m。
单击FourLines.h:
#define kField1Key @"Field1"
#define kField2Key @"Field2"
#define kField3Key @"Field3"
#define kField4Key @"Field4"
遵循 <NSCoding,NSCopying>协议,并声明 NSString *field1,field2,field3,field4;实例变量
单击FourLines.m:
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:field1 forKey:kField1Key];
[encoder encodeObject:field2 forKey:kField2Key];
[encoder encodeObject:field3 forKey:kField3Key];
[encoder encodeObject:field4 forKey:kField4Key];
}
- (id)initWithCoder:(NSCoder *)decoder
{
if (self=[super init])
{
self.field1=[decoder decodeObjectForKey:kField1Key];
self.field2=[decoder decodeObjectForKey:kField2Key];
self.field3=[decoder decodeObjectForKey:kField3Key];
self.field4=[decoder decodeObjectForKey:kField4Key];
}
return self;
}
- (id)copyWithZone:(NSZone *)zone
{
FourLines *copy=[[[self class] allocWithZone:zone] init];
copy.field1=[self.field1 copy];
copy.field2=[self.field2 copy];
copy.field3=[self.field3 copy];
copy.field4=[self.field4 copy];
return copy;
}
@end
11.5.2 实现PersistenceViewController类
单击PersistenceViewController.h:删除原来的#define,声明新的#define
#define kFileName @"archive"
#define kDataKey @"Data"
切换到PersistenceViewController.m:
- (void)applicationWillTerminate:(NSNotification *)notification
{
FourLines *fourLines=[[FourLines alloc] init];
fourLines.field1=field1.text;
fourLines.field2=field2.text;
fourLines.field3=field3.text;
fourLines.field4=field4.text;
NSMutableData *data=[[NSMutableData alloc] init];
NSKeyedArchiver *archiver=[[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:fourLines forKey:kDataKey];
[archiver finishEncoding];
[data writeToFile:[self dataFilePath] atomically:YES];
[fourLines release];
[archiver release];
[data release];
}
- (void)viewDidLoad {
NSString *filePath=[self dataFilePath];
if ([[NSFileManager dafaultManager] fileExistsAtPath:filePath])
{
NSData *data=[[NSMutableData alloc] initWithContentsOfFile:[self dataFilePath]];
NSKeyedUnarchiver *unarchiver=[[NSKeyedUnarchiver alloc] initForReadingWithData:data];
FourLines *fourLines=[unarchiver decodeObjectForKey:kDataKey];
[unarchiver finishDecoding];
field1.text=fourLines.field1;
field2.text=fourLines.field2;
field3.text=fourLines.field3;
field4.text=fourLines.field4;
[unarchiver release];
[data release];
}
UIApplication *app=[UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillTerminate:)
name:UIApplicationWillTerminateNotfication
object:app];
[super viewDidLoad];
}
11.6 使用iPhone的嵌入式SQLite3
创建和打开数据库 代码:
sqlite3 *database;
int result=sqlite3_open("/path/to/database/file",&database);//如果指定位置不存在数据库,则他会创建一个新的数据库
如果result==SQLITE_OK,则表示数据库已成功打开。此处你应该记住的一件事情就是,数据库文件的路径必须做为C字符串(而非
NSString)传递。SQLite3是采用可移植的C(而非Obj-C)编写的,他不知道什么是NSString。所幸,有一个NSString方法,该方法
从NSString实例生成C字符串:
char *cStringPath=[pathString UTF8String];
当你对SQLite3数据库执行完所有操作之后,通过调用以下内容来关闭数据库:
sqlite3_close(database);
使用sqlite3_exec:
char *errorMsg;
const char *createSQL="CREATE TABLE IF NOT EXISTS PEOPLE (ID INTEGER PRIMARY KEY AUTOINCREMENT,FIELD_DATA TEXT)";
int result=sqlite3_exec(database,createSQL,NULL,NULL,&errorMSG);
如果result==SQLITE_OK表示命令成功运行,否则errorMSG将包含对所发生问题的描述。
函数sqlite3_exec用于针对SQLite3运行任何不返回数据的命令。它用于执行更新、插入和删除操作。
从数据库中检索数据有点麻烦。必须首先通过向其输入SQL SELECT命令来准备该语法:
NSString *query=@"SELECT ID,FIELD_DATA FROM FIELDS ORDER BY ROW";
sqlite3_stmt *statement;
int result=(sqlite3_prepare_v2(database,[query UTF8String],-1,&statement,nil);
如果result==SQLITE_OK,则你的语句已成功准备,并且你可以开始单步调试结果集。下面是一个单步调试结果集并从数据库中检索
int和NSString的示例:
while (sqlite3_step(statement)==SQLITE_ROW) {
int rowNum=sqlite3_column_int(statement,0);
char *rowData=(char *)sqlite3_column_text(statement,1);
NSString *fieldValue=[[NSString alloc] initWithUTF8String:rowData];
[fieldValue release];
}
sqlite3_finalize(statement);
设置项目使用SQLite3
我们已经讲述了基本知识,下面介绍在实践中的工作原理。我们将再次改进Persistence应用程序,这次使用SQLite3来存储他的数
据。他将使用一个表并将字段值存储在该表中的4个不同的行中。我们将为每个行提供一个与其字段相对应的行号,例如,field1中
的值将存储在表中行号为1的行中。
通过一个过程API来访问SQLite3,该API提供对很多C函数调用的接口。要使用此API,我们需要将应用程序链接到一个名为
libsqlite3.dylib的动态库。在Mac OS X和iPhone上,该库位于/usr/lib中。
在Xcode中打开Persistence应用程序。在Groups&Files窗格中选择Frameworks。Project=>Add to Project...。然后导航到
/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator2.1.sdk/usr/lib,并找到名为
libsqlite3.dylib的文件。当系统弹出提示,确保取消选中标签为Copy item3 into destination group's folder (if needed)的
复选框。还要确保将Reference Type改为Relative to Current SDK。注意,该目录可能有多个以libsqlite3开头的其他条目。无比
选择libsqlite3.dylib。他是始终指向最新版本的SQLite3库的别名。
接下来,对PersistenceViewController.h进行以下更改:
#import "/usr/include/sqlite3.h"
#define kFileName @"data.sqlite3"
@interface PersistenceViewController:UIViewController {
...
sqlite3 *database;
}
切换到PersistenceViewController.m:
- (void)applicationWillTerminate:(NSNotification *)notification
{
for (int i=1;i<=4;i++)
{
NSString *fieldName=[[NSString alloc] initWithFormat:@"field%d",i];
UITextField *field=[self valueForKey:fieldName];
[fieldName release];
NSString *update=[[NSString alloc] initWithFormat:@"INSERT OR REPLACE INTO FIELDS (ROW,FIELD_DATA) VALUES (%d,'%@');",i,field.text];
char *errorMsg;
if (sqlite3_exec (database,[update UTF8String],NULL,NULL,&errorMsg) !=SQLITE_OK) {
NSAssert1(0,@"Error Updating tables:%s",errorMsg);
sqlite3_free(errorMsg);
}
}
sqlite3_close(database);
}
- (void)viewDidLoad {
if (sqlite3_open([[self dataFilePath] UTF8String],&database) != SQLITE_OK) {
sqlite3_close(database);
NSAssert(0,@"Failed to open database");
}
char *errorMsg;
NSString *createSQL=@"CREATE TABLE IF NOT EXISTS FIELDS (ROW INTEGER PRIMARY KEY,FIELD_DATA TEXT);";
if (sqlite3_exec (database,[createSQL UTF8String],NULL,NULL,&errorMsg) !=SQLITE_OK) {
sqlite3_close(database);
NSAssert1(0,@"Error creating table :%s",errorMsg);
}
NSString *query=@"SELECT ROW,FIELD_DATA FROM FIELDS ORDER BY ROW";
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(database,[query UTF8String],-1,&statement,nil)==SQLITE_OK) {
while (sqlite3_step(statement)==SQLITE_ROW) {
int row=sqlite3_column_int(statement,0);
char *rowData=(char *)sqlite3_column_text(statement,1);
NSString *fieldName=[[NSString alloc] initWithFormat:@"field%d",row];
NSString *fieldValue=[[NSString alloc] initWithUTF8String:rowData];
UITextField *field=[self valueForKey:fieldName];
field.text=fieldValue;
[fieldName release];
[fieldValue release];
}
sqlite3_finalize(statement);
}
...
}
解析:
在applicationWillTerminate:方法中使用了断言,NSAssert1,之所以使用断言,而不使用异常或手动检查错误,是因为这种情况只有在我们开发人员出错的情况下才会出现。使用此断言宏将有助于我们调试代码,并且可以脱离最终的应用程序。如果某个错误条件是用户正常情况下可能遇到的条件,则可能应该使用某些其他形式的错误检查。
- iPhone开发基础教程笔记(十一)--第十一章 基本数据持久性(属性列表、归档对象、SQLite3)
- Iphone开发基础教程 (11章 基本数据持久性)--读书笔记
- 对象归档、SQLite3、Core Data和 属性列表(数据持久化)
- iphone开发中数据存储 属性列表,归档,SQLite3,core data 还有FMDB应该选择哪种?
- IOS开发指南学习——数据持久化(属性列表、对象归档、SQLite)
- phone开发基础教程》第十一章 基本数据持久
- 数据持久化方式:属性列表、对象归档、SQLite3和Core Data
- iPhone开发基础教程笔记(一)--第一章和第二章-创建基本项目程
- iPhone开发基础教程笔记(二)--第三章 处理基本交互
- iPhone开发基础教程笔记(一)--第一章和第二章-创建基本项目
- iOS中的数据持久化方式,基本上有以下四种:属性列表、对象归档、SQLite3和Core Data
- 对象归档/属性列表
- iPhone开发基础教程笔记(十五)--第十五章 加速计
- iPhone开发基础教程笔记(十七)--第十七章 应用程序本地化
- iphone开发中数据持久化之——模型对象归档(二)
- iOS 数据持久性存储--属性列表存储
- iOS 数据持久性存储--属性列表存储
- Iphone开发(十一)从plist文件读取列表数据并添加索引
- 在ubuntu下如何验证文件的MD5码
- 从myspace数据库看分布式系统数据结构变迁
- 解决vs2005在IE8下不能调试问题
- 动态修改listview,spinner控件字体大小的解决办法
- 全面兼容IE6/IE7/IE8/FF的CSS HACK写法
- iPhone开发基础教程笔记(十一)--第十一章 基本数据持久性(属性列表、归档对象、SQLite3)
- Zookeeper开源客户端框架Curator简介
- UIAlertView+UIActivityIndicatorView
- iOS extern使用教程
- java httpclient模拟login
- 常用的一些正则验证
- 通过/proc/net/dev分析网络包量,流量,错包,丢包
- Objective-C 10天学习_第三天
- 【Open Flash Chart2.0】Ajax + PHP 获取图表