您的位置:首页 > 数据库

IOS沙盒和sqlite简单操作分析

2013-04-08 14:40 405 查看
每一个IOS应用都有其自己的独立存储数据的磁盘空间,这个空间不允许其他应用访问,当然本应用也不能访问其他应用的存储空间。这个空间在IOS中叫做应用沙盒,其类似于WP中的独立存储空间。

沙盒的目录结构如下:



下面简要介绍一下各个目录的作用:

Document目录一般用来保存非常重要的数据但不能太大(数据太大同步到云端需要很长时间),因为该目录可以通过itunes同步到icloud上,苹果建议将程序中建立的或在程序中浏览到的文件数据保存在该目录下,iTunes备份和恢复的时候会包括此目录;

tmp主要用来保存临时数据,应用关闭后,该目录下的数据会被清空;

Library的作用和Document类似,存储程序的默认设置或其它状态信息。其Preference一般保存用户的偏好设置,通过API可以非常便捷的访问该空间内存储的数据(后面的例子我们将会看到)。Caches主要用来存放缓存文件,itunes不会备份该目录,该目录在应用退出时也不会被删除;

那么我们如何来利用沙箱进行文件的存储和读取呢?

首先我们展示如何将一个Person对象保存到一个文件中(这里的扩展名是可以随便写的,这里使用的是archive方式进行的归档持久化,其实质上并不是一个.plist文件,我们知道.plist文件实质上是一个xml文件。而archive方式进行归档后的文件并不是xml格式),代码如下:

- (void)createPerson
{
Person *one = [Person initPersonWithName:"fanly" age:22 heigth:1.7];
//获取Document路径,并在其下创建一个person.plist文件
NSString *path = [@"person.plist" documentAppend ];
[NSKeyedArchiver archiverRootOjbect:person toFile:path];
}

上面代码中documentAppend方法是我们自定义的NSString的一个分类来实现的,其实现的代码如下:

- (NSString *)documentAppend
{
//查明它到底返回的是什么
//注意此行调用到了C的函数,该函数将通过传入的参数值(两个枚举)来返回不同的目录,此处他将放回Document下的所有子目录
//因为我们的例子只有一个目录,所以得到最后一个对象即可
NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask), YES), lastObject];
return [documents stringByAppendingPathComponent:self];
}


那么是不是所有的对象都可以被存储到文件中呢?cocoa会聪明到知道用户的自定义的类如何转换成文件呢?答案显然是否定的,那么例子里的对象是如何被存储的呢?这是因为我们的Person对象实现了NSCoding协议,我们实现了协议的如下两个方法,这两个方法就告诉了cocoa如何持久化对象到文件中。方法如下:

- (id)initWithCoder:(NSCoder *)decoder
{
self.name = [decoder decodeObjectForKey:@"name"];
return self;
}

- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encoderObject:self.name forKey:@"name"];
}


这两个方法只是对Person的那么进行了编码和解码,自然的当我们调用[NSKeyedArchiver archiverRootOjbect:person toFile:path]进行持久化,和调用[NSKeyedUnarchiver unarciverObjectFromFile:path]自然也只能持久化和反持久化我们进行了编码和解码的属性。那些没有进行编码和解码的属性是不会被持久化的。这里需要特别注意的是,如果你的类继承了其他的类,并且这个类也实现了NSCoding协议,那么在子类实现NSCoding协议的相关方法里应该先调用父类的相关方法,这样才会使得持久化和反持久化结果正确。

我们知道在cocoa中有些类是可以直接写到.plist文件中的,例如下面这样的代码段:

- (void)createPlist
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];

[dict setObject:@"fanly" forKey:@"name"];
[dict setObject:[NSMumber numberWithInt:12] forKey:@"age"];
[dict setObject:@"ugarden" forKey:@"company"];

[dict writeToFile:[@"person.plist" documentAppend] atomically:YES];
}


像NSArray,NSString都有类似的方法。这里需要注意的是,你往字典或者数组里面添加的对象也必须实现NSCoding协议,不然的话持久化也是会失败的。你虽然得不到任何错误信息,但是文件里面不会有任何数据。好奇心强的同学可以试一试。

以上通过 archive的方式 一次只能归档一个类,那么我想多个类一次性写到同一个文件中,我们该如何做呢?我们当然可以通过将实现了NSCoding协议的对象放到一个字段或者数组中然后利用字典或者数组的writeToFile方法将对象写到.plist文件中。那么能不能通过archive的方式实现呢?答案是肯定的,刚才的需求我们可以通过借助NSData或者NSMutableData来实现,NSData可以用来保存二进制数据,有点类似于java中的字节数组,不管你是什么类型的对象,它都是可以保存的。写入NSData中的数据会保存在内存中,然后配合NSKeyedArchiver对象就可以将对象通过archive的方式归档到文件中。当然再次强调前提,你的对象必须实现NSCoding协议。示例代码如下:

- (void)createPersons
{
Person *one = [Person initPersonWithName:@"fanly" age:23  heigth:1.7];
Person *tow = [Person initPersonWithName:@"frank" age:24 heigth:1.55];

NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] iniForWritingWithMutableData:data];

[archiver encodeObject:one forKey:@"ong"];
[archiver encodeObject:tow forKey:@"tow"];
[archiver finishEncoding];

[data writeToFile:[@"persons.data" documentAppend] atomically:YES];

[archiver release];
}

- (void)readPersons
{
NSMutableData *data = [NSMutableData data];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];

Person *one = [unarchiver decodeObjectForKey:@"one"];
Person *tow = [unarchiver decodeObjectForKey:@"tow"];
[unarchiver finishDecoding];

[unarchiver release];
}


下面我们看看如何便捷地将数据存储到Library/Preference目录中,示例代码如下:

- (void)savePreference
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@"fanly" forKey:@"name"];
[defaults setFloat:186 forKey:@"text_size"];
[defaults setBool:YES forKey:@"auto_login"];
[defaults synchronize];
}

- (void)readPreference
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL autoLogin = [defaults boolForKey:@"auot_login"];

NSLog(@"autoLogin:%i", autoLogin);
}


注意,在归档和反归档的时候都有个finish方法,必须调用,不然你会得到一个exception。

之前我们给一个对象实现一个copy方法的时候可能要写很多的代码,现在我们展示如何巧妙地利用NSData和NSArchiver来实现一个对象的深复制。

- (Person *)copy
{
NSData *data = [NSKeyedArchiver archiverDataWithRootObject:self];
Person *tow = [NSKeyedUnarchiver unarciverObjectWithData:data];

NSLog(@"0x%x-one:%@", self, self);
NSLog(@"0x%x-tow:%@", tow, tow);

return tow;
}



每一个应用都有且只有一个NSUserDefaults实例,我们通过这个对象就可以轻松地将数据存储到应用的Library/Preference目录下,也可以通过这个NSUserDefaults实例提供的方法进行读取。

自然这里也是可以存入自定义的类的,自然也是需要实现NSCoding协议的。

下面介绍一下在IOS中如何操作sqlite数据库。

首先我们要获得数据库的运行时实例,在IOS中获得sqlite和操作相关API都是C语言的函数,所以语法比较复杂一些。下面的代码将获得一个数据库实例。

//这是一个全局的实例变量,不要丢了哦
sqlite3 *db;
- (void)openSQLite
{
//该方法将在Document目录下产生一个数据库文件
NSString *databaseFileName = [@"sqlist.sql" documentAppend];
//这个方法的第二参数非常关键,如果获得链接的过程顺利,那么该方法会将数据库运行时的实例的地址修改db指针原来值,这就是我们为何要传入指针的地址值的原因。他的返回值是一个状态码,状态还有其他很多类型,请大家自行查看API进行了解。
int result = sqlite3_open([databaseFileName UTF8String], &db);

if (result != SQLITE_OK) {
NSLog(@"open database failed!");
}
}


获得数据库运行时实例后我们就可以通过该实例来操作数据库了。现在我们来创建一张表吧,代码如下:

- (void)createTable
{
char *sql = "create table if not exists t_person(id integer primary key autoincrement, name text, age integer)";
char *error;
int result = sqlite3_exec(db, sql, NULL, NULL, &error);

if (result != SQLITE_OK) {
NSLog(@"create table error:%s", error);
}
}


接下来,我们在刚才创建的数据表中插入一条数据,代码如下:

- (void)inserData
{
char *sql = "insert into t_person(name, age) values(?, ?);";

for (int i = 0; i < 30; i++ ) {
sqlite3_stmt *stmt;

int result = sqlite3_prepare(db, sql, -1, &stmt, NULL);

if (result != SQLITE_OK) {
NSLog(@"insert person to t_person fail");
} else {

NSString *name = [NSString stringWithFormat:@"fanly-%i", i];
sqlite3_bind_test(stmt, 1, [name UTF8String], -1, NULL);
sqlite3_bind_int(stmt, 2, 23 + i);

if ((sqlite3_step(stmt) == SQLITE_DONE)) {
NSLog(@"insert person success!")
} else {
NSLog(@"insert person faild!");
}
}
sqlite3_finalize(stmt);
}
}

注意这里进行参数替换的时候下标是从1开始的。

下面将读取我们刚才写入的数据:

- (void)findData
{
sqlite3_stmt *stmt;
char *sql = "select * from t_person;";

int result = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (result == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
int ID = sqlite3_column_int(stmt, 0);
char *name = (char *)sqlite3_column_text(stmt, 1);
int age = sqlite3_column_int(stmt, 2);

NSLog(@"ID:%i, name:%s, age:%i", ID, name, age);
}
}

sqlite3_finalize(stmt);
}

可见如果,有多条符合条件的结果时,我们要多次执行sqlite3_step方法才能遍历出多条记录。

注意这里取得结果集中的数据时,下标是从0开始,和参数绑定的时候不同。

最后,在操作完数据库之后,记得关闭链接。

sqlite3_close(db);


这里并没有,展示删除或者更新的操作。据一些资料说通过sqlite3_exec可以执行更新和删除操作,我在写这篇日志的时候还没有验证。明天会验证。还有很多sqlite3的函数值得我们去探索,有了现有的知识,再去弄清楚其他函数的作用还是很容易的事,至少我们已经知道在什么时候我们能做什么。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: