iOS开发中的本地数据存储(持久化)

问题:如何把一个包含自定义对象的数组序列化到磁盘?

涉及的知识点:iOS开发中的本地数据存储(持久化)

一、iOS开发中本地存储主要有三种形式
1、plist文件(属性列表)
2、preference(偏好设置)
3、NSKeyedArchiver(归档)
4、SQLite 3
5、CoreData

我们先来了解一下沙盒,每个应用的沙盒是相对独立。iOS本地化存储的数据保存在沙盒中。
Documents:iTunes会备份该目录。一般用来存储需要持久化的数据。
Library/Caches:缓存,iTunes不会备份该目录。内存不足时会被清除,应用没有运行时,可能会被清除,。一般存储体积大、不需要备份的非重要数据。
Library/Preference:iTunes同会备份该目录,可以用来存储一些偏好设置。
tmp: iTunes不会备份这个目录,用来保存临时数据,应用退出时会清除该目录下的数据。

1、plist文件

(1)简介
plist文件是将某些特定的类通过xml文件的方式保存在目录中。可以被序列化的类型一般为OC提供的基本类型,如下:NSArrasy(NSMutableArray)、NSDictionary(NSMutableDictionary)、NSDate(NSMutableDate)、NSString(NSMutableString)、NSNumber、NSDate。保存在沙盒的Documents中。
(2)使用方法

a.获得文件路径 
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [path stringByAppendingPathComponent:@ "123.plist" ];

b.存储
NSArray *array = @[@ "123" , @ "456" , @ "789" ];
[array writeToFile:fileName atomically:YES];

c.读取
NSArray *result = [NSArray arrayWithContentsOfFile:fileName];
NSLog(@ "%@" , result);

(3)部分参数介绍

  • 存储时使用**writeToFile: atomically:**方法。 其中atomically表示是否需要先写入一个辅助文件,再把辅助文件拷贝到目标文件地址。这是更安全的写入文件方法,一般都写YES。
  • 读取时使用**arrayWithContentsOfFile:**方法
2 、preference(偏好设置)

(1)介绍:
NSUserDefaults适合存储轻量级的本地数据,用来保存应用程序设置和属性、用户保存的数据,比如要保存一个登陆界面的数据,用户再次打开程序或开机后这些数据仍然存在。NSUserDefaults可以存储的数据类型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。如果要存储其他类型,则需要转换为前面的类型,才能用 NSUserDefaults 存储,比如自定义对象,需要将对象转化为NSData在保存。
(2)使用实例

//将对NSUserDefault操作封装了两个方法,避免代码中到处是对NSUserDefault的操作
//存储数据
-(void)saveToUserDefaults:(NSString*)tosaveedString withKey:(NSString *)tosaveedKey
{
    NSUserDefaults * tmp = [NSUserDefaults standardUserDefaults];
    if (tmp) {
        [tmp setObject:tosaveedString forKey:tosaveedKey];
        [tmp synchronize];
    }
}

//读出数据
-(NSString *)restoreFromUserDefaults:(NSString *)key
{
    NSString * rtn = nil;
    NSUserDefaults * tmp = [NSUserDefaults standardUserDefaults];
    if (tmp) {
        rtn = [tmp objectForKey:key];
    }
    return rtn;
}


//存入数据
[self saveToUserDefaults:textPass.text withKey:@"saveUserPass"];
//使用读出数据
 NSString *isLoginCode=[self restoreFromUserDefaults:@"saveUserPass"];

注意

  • 偏好设置是专门用来保存应用程序的配置信息的,一般不要在偏好设置中保存其他数据。
  • 如果没有调用synchronize方法,系统会根据I/O情况不定时刻地保存到文件中。所以如果需要立即写入文件的就必须调用synchronize方法。
  • 偏好设置会将所有数据保存到同一个文件中。即preference目录下的一个以此应用包名来命名的plist文件。

3、NSKeyedArchiver(归档)
(1)介绍
自定义对象不能直接写入文件,保存到磁盘,我们需要使用
NSKeyedArchiver将对象archive为NSData类型的数据,如果数据模型支持archive,那么数据模型类需要遵守NSCoding协议,并提供encodeWithCoder:和initWithCoder:方法。前一个方法告诉系统怎么对对象进行编码,而后一个方法则是告诉系统怎么对对象进行解码。
(2)实例:自定义对象序列化到磁盘

Possession类
@interface Possession:NSObject<NSCoding>{//遵守NSCoding协议
    NSString *name;//待归档类型
}
@implementation Possession
-(void)encodeWithCoder:(NSCoder *)aCoder{
    [aCoder encodeObject:name forKey:@"name"];
}

-(void)initWithCoder:(NSCoder *)aDecoder{
      name=[aDeCoder decodeObjectforKey:@"name"];
}

master类
@interface Master:NSObject<NSCoding>{//遵守NSCoding协议
    Possession *possession;
    NSString     *position;
}
@implementation Possession
-(void)encodeWithCoder:(NSCoder *)aCoder{
    [aCoder encodeObject:possession forKey:@"possession"];//Possession类遵循NSCoding协议,可以归档
    [aCoder encodeObject:position forKey:@"position"];
}

-(void)initWithCoder:(NSCoder *)aDecoder{
      possession=[aDeCoder decodeObjectforKey:@"possession"];
      position= [aDeCoder decodeObjectforKey:@"position"];
}




归档操作:
如果对Possession对象allPossession归档保存,只需要使用NSKeyedArchiver的方法archiveRootObject:toFile: 即可。

NSString *path = [self possessionArchivePath];
[NSKeyedArchiver archiveRootObject:allPossessions toFile: path ]

解档操作:
使用NSKeyedUnarchiver的方法unarchiveRootObject:toFile: 即可

allPossessions = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] retain];

缺点:归档的形式来保存数据,只能一次性归档保存以及一次性解压。所以只能针对小量数据,而且对数据操作比较笨拙,即如果想改动数据的某一小部分,还是需要解压整个数据或者归档整个数据。
注意
(1)编码解码要同步:新版本增加一个可以key,那么要在 initWithCoder: 和 decodeWithCoder: 同时操作,处理好这个新的属性的归档、解档。

(2) 提供向后兼容性,即新、旧版本户型读取彼此的文档,实现向后兼容性就需要知道旧版本的编解码。然后按照找不到key的情况,处理新旧版本互读。

找不到key的情况:例如新版本增加一个属性,那么新版本在解码老版本的归档对象时,会出现找不到key的情况,此时系统会给一个默认值,如果默认值不满足需求可以使用 containsValueForKey 查看是否有对应的 key ,如果有正常解档该属性,没有的话可以按照自己的需求处理,如给其一个默认值或者不处理。

(3)提供向前兼容性,即对于未来可能更改的属性,给一个默认值。比如说某衣服现在有5种样式, 未来大有可能增加新的样式, 那么在解码时检测这个样式是否是当前允许的值, 如果不是, 给一个默认值, 这样在以后的扩展中 老版本仍可正常表现.
(4)Key 在当前对象的范围内应是唯一的,在子类使用的Key可能与其父类使用的Key 相冲突.因此, 在框架的公共类中, 应使用带前缀的字符串, 以避免冲突,前缀可以是类名或者包名。
(5)如果一个自定义的类 A,作为另一个自定义类 B 的一个属性存在;那么,如果要对 B 进行归档,那么,B 要实现 NSCoding 协议。并且,A 也要实现 NSCoding 协议。
(6)尽量减少编码和解码的对象数量, 这样写或读时的速度就会更快.

4、SQLite 3

之前的所有存储方法,都是覆盖存储,即后期想在文件中增加一条数据必须把整个文件读出来,修改完成后再把整个内容覆盖写入文件,这个特性使它们所以不适合存储大量的内容,存储大量数据时使用轻型数据库SQLite,它占用率资源低且处理速度快。
(1)字段类型
对于使用者而言,SQLite将数据分为以下几种类型:

  • integer : 整数
  • real : 实数(浮点数)
  • text : 文本字符串
  • blob : 二进制数据,比如文件,图片之类的

    实际上SQLite是无类型的。即不管你在创表时指定的字段类型是什么,存储时依然可以存储任意类型的数据。而且在创表时也可以不指定字段类型。SQLite之所以设计字段类型是为了良好的编程规范和方便开发人员交流,所以平时在使用时最好设置正确的字段类型!主键必须设置成integer

(2)准备工作
导入依赖库啦,在iOS中要使用 SQLite3,需要添加库文件libsqlite3.dylib并导入主头文件。

(3)使用

  • 创建数据库并打开
    操作数据库之前必须先指定数据库文件和要操作的表,所以使用SQLite3,首先要打开数据库文件,然后指定或创建一张表。
//打开数据库并创建一个表
-(void)openDatabase {
//1.设置文件名
NSString *filename = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.db" ];
//2.打开数据库文件,如果没有会自动创建一个文件
NSInteger result = sqlite3_open(filename.UTF8String, &_sqlite3);
    if  (result == SQLITE_OK) 
    {
        NSLog(@ "打开数据库成功!" );
        //3.创建一个数据库表
        char *errmsg = NULL;
        sqlite3_exec(_sqlite3,  "CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)" , NULL, NULL, &errmsg);
        if  (errmsg) {
            NSLog(@ "错误:%s" , errmsg);
        }  else  {
            NSLog(@ "创表成功!" );
        }
    }else{
        NSLog(@ "打开数据库失败!" );
    }
}
  • 执行指令
    使用 sqlite3_exec() 方法可以执行任何SQL语句,比如创表、更新、插入和删除操作。但是一般不用它执行查询语句,因为它不会返回查询到的数据。
//往表中插入1000条数据
-(void)insertData {
  NSString *nameStr;
  NSInteger age;
  for (NSInteger i = 0; i < 1000; i++) {
   nameStr = [NSString stringWithFormat:@ "Bourne-%d" , arc4random_uniform(10000)];
   age = arc4random_uniform(80) + 20;
   NSString *sql = [NSString stringWithFormat:@ "INSERT INTO t_person (name, age) VALUES('%@', '%ld')" , nameStr, age];
   char *errmsg = NULL;
   sqlite3_exec(_sqlite3, sql.UTF8String, NULL, NULL, &errmsg);
     if  (errmsg) {
         NSLog(@ "错误:%s" , errmsg);
     }
  }
  NSLog(@ "插入完毕!" );
}
  • 查询指令
    查询相对比较麻烦,为得到查询数据必须要获得查询结果,需使用sqlite3的下面函数
    • sqlite3_prepare_v2() : 检查sql的合法性。
    • sqlite3_step() : 逐行获取查询结果,不断重复,直到最后一条记录,
    • sqlite3_coloum_xxx() : 获取对应类型的内容,iCol对应的就是SQL语句中字段的顺序,从0开始。根据实际查询字段的属性,使用sqlite3_column_xxx取得对应的内容即可
    • sqlite3_finalize() : 释放stmt
//从表中读取数据到数组中
-(void)readData {
  NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1000];
  char *sql =  "select name, age from t_person;" ;
  sqlite3_stmt *stmt;
  NSInteger result = sqlite3_prepare_v2(_sqlite3, sql, -1, &stmt, NULL);//检查sql合法性
  if  (result == SQLITE_OK) 
  {   //while循环,使用sqlite3_step()逐行查询
      while  (sqlite3_step(stmt) == SQLITE_ROW) {
        char *name = (char *)sqlite3_column_text(stmt, 0);//sqlite3_column_xx获取结果
        NSInteger age = sqlite3_column_int(stmt, 1);
        //创建对象
        Person *person = [Person personWithName:[NSString stringWithUTF8String:name] Age:age];
        [mArray addObject:person];
      }
      self.dataList = mArray;
  }
  sqlite3_finalize(stmt);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员的修养

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值