iOS 数据持久化-四种存储方式(二)

上一篇:iOS 数据持久化-沙盒机制介绍(一)

iOS 数据存储有四种方案:

NSUserDefault

KeyChain(钥匙串,删除App,数据不会删除)

File(plist、Archive)

DB(SQLite、FMDB、CoreData)

NSUserDefault

Preference偏好设置,NSUserDefaults(plist),用来保存应用程序设置和属性。

KeyChain:钥匙串

1 . 更安全. 对比 NSUserDefault 存储一些数据, 会更加安全.

2 . 即便 App 被卸载, 存储的信息依旧存在, 再次安装App, 存储是信息依旧可以使用.

3 . 相同的 Team ID 开发, 可实现多个App 共享数据

File

plist:存储plist文件,plist文件打开,也是xml形式。(NSArray,NSDictionary writeToFile - <NSCoding>),不能存储用户自定义对象

Archive:采用归档的形式来保存数据,该数据对象需要遵守NSCoding协议。归档形式保存数据只能一次性归档保存以及一次性解压,只能针对小量数据,而且数据操作笨拙,即如果想改动数据的某一小部分,需要解压整个数据或者归档整个数据。

DB(SQLite、FMDB、CoreData):

相对前两种更为复杂,但也最好用。SQLite,可以自己封装,也可以使用FMDB等第三方封装库,还可以使用系统的CoreData


NSUserDefaults

用于存储用户的偏好设置和用户信息,如用户名,是否自动登录,字体大小等.

数据自动保存在沙盒的Libarary/Preferences目录下.

NSUserDefaults将输入的数据储存在.plist格式的文件下,这种存储方式就决定了它的安全性几乎为0,所以不建议存储一些敏感信息如:用户密码,token,加密私钥等!

它能存储的数据类型为:NSNumber(NSInteger、float、double),NSString,NSDate,NSArray,NSDictionary,BOOL.

不支持自定义对象的存储.

NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];

[userDefault setInteger:1 forKey:@"integer"];

[userDefault setBool:YES forKey:@"BOOl"];

[userDefault setFloat:6.5 forKey:@"float"];

[userDefault setObject:@"123" forKey:@"numberString"];

NSString *numberString = [userDefault objectForKey:@"numberString"];

BOOL myBool = [userDefault boolForKey:@"BOOl"];

[userDefault removeObjectForKey:@"float"];

[userDefault synchronize];

需要注意的问题:

NSUserDefaults存储的数据都是不可变的,想将可变数据存入需要先转为不可变才可以存储.

NSUserDefaults是定时把缓存中的数据写入磁盘的,而不是即时写入,为了防止在写完NSUserDefaults后程序退出导致的数据丢失,可以在写入数据后使用synchronize强制立即将数据写入磁盘.

KeyChain(钥匙串,删除App,数据不会删除)

用于本地重要数据的存储,将数据加密后存储在本地更安全.如:密码,秘钥,序列号等.当你删除APP后Keychain存储的数据不会删除,所以在重装App后,Keychain里的数据还能使用。从ios 3.0开始,跨程序分享keychain变得可行而NSUserDefaults存储的数据会随着APP而删掉.

使用keychain时苹果官方已经为我们封装好了文件KeychainItemWrapper,引入即可使用。当然也可是使用其他优秀的第三方的封装。

File

1.属性列表(plist文件)

即属性列表文件,全名是Property List,这种文件的扩展名为.plist,因此,通常被叫做plist文件。它是一种用来存储串行化后的对象的文件,用于存储程序中经常用到且数据量小而不经常改动的数据。

可以存储的类型:NSNumber,NSString,NSDate,NSData ,NSArray,NSDictionary,BOOL.

不支持自定义对象的存储.

plist的创建方式有两种:command + n 创建和纯代码创建,不同的创建方式使用方法也自然不同

command + n 创建:

创建:(创建方法自行百度)

读取:

NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"myTest" ofType:@"plist"];

    NSMutableDictionary *dataDic = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];

    /**如果为数组时*/

//    NSMutableArray *dataArray = [NSMutableArray arrayWithContentsOfFile:plistPath];

    NSLog(@"%@",dataDic);

修改:

[dataDic setObject:@"男" forKey:@"sex"];

[dataDic setObject:@15 forKey:@"age"];

[dataDic writeToFile:plistPath atomically:YES];

NSLog(@"----%@",dataDic);

纯代码创建:

创建:

NSArray *sandBoxPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

NSString *documentsPath = [sandBoxPath objectAtIndex:0];

NSString *plistPath = [documentsPath stringByAppendingPathComponent:@"myTestPlist.plist"];

NSLog(@"%@",plistPath);

写入:

NSMutableDictionary *dic = [NSMutableDictionary dictionary];

[dic setObject:@"18" forKey:@"age"];

[dic setObject:@"胡杨" forKey:@"name"];

[dic writeToFile:plistPath atomically:YES];

读取:

NSArray *sandBoxPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    NSString *documentsPath = [sandBoxPath objectAtIndex:0];

    NSString *plistPath = [documentsPath stringByAppendingPathComponent:@"myTestPlist.plist"];

    NSLog(@"%@",plistPath);

    NSMutableDictionary *dataDic = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];

    NSLog(@"-----%@",dataDic);

修改:

[dataDic setObject:@"男" forKey:@"sex"];

[dataDic setObject:@15 forKey:@"age"];

[dataDic writeToFile:plistPath atomically:YES];

NSLog(@"----%@",dataDic);

需要注意的问题:

如果需要存储自定义类型的数据需要先进行序列化!

我们使用了属性列表来指定应用的默认设置与相应的数据存储,并且方便使用Xcode或者Property List Editor应用手动编辑它们,只要字典或者数据包含特定可序列化对象,就可以NSDictionary和NSArray实例写入属性列表或者从属性列表创建相应的对象;

什么是序列化对象?

序列化对象(Serialized objects),是指可以被转换为字节流以便于存储到文件中或者通过网络进行传输的对象;

虽然说任何对象都可以被序列化,但是只有某些特定的对象才能放置到某个集合类(例如:

NSArray;

NSMutableArray;

NSDictionary;

NSMutableDictionary;

NSData;

NSMutableData;

NSString;

NSMutableString;

NSNumber;

NSDate;

中,并使用该集合类的方法在属性列表存储中使用,其他的对象也可以使用归档的方法进行存储(在对象的归档、解档我们会进行详细介绍)。

注意

只有以上列出的类型才能使用plist文件存储。

存储时使用writeToFile: atomically:方法。 其中atomically表示是否需要先写入一个辅助文件,再把辅助文件拷贝到目标文件地址。这是更安全的写入文件方法,一般都写YES。

读取时使用arrayWithContentsOfFile:方法。

plist文件是将某些特定的类,通过XML文件的方式保存在目录中。

-(void)writeToFile:(NSMutableDictionary *)data

{

    // 获取当前沙盒中的文件路径

    NSArray *paths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask , YES);

    NSLog(@"paths = %@",paths);

    // 获取Document目录路径

    NSString *documentPath = [paths objectAtIndex:0];

    // 设置文件的路径

    NSString *filePath = [documentPath stringByAppendingPathComponent:@"newdata.plist"];

    [data writeToFile:filePath atomically:YES];

}

-(NSMutableDictionary *)readFile

{

    // 获取当前沙盒中的文件路径

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask , YES);

    NSLog(@"paths = %@",paths);

    // 获取Document目录路径

    NSString *documentPath = [paths objectAtIndex:0];

    // 设置文件的路径

    NSString *filePath = [documentPath stringByAppendingPathComponent:@"newdata.plist"];

    // 文件管理类

    NSFileManager *fm = [NSFileManager defaultManager];

    // 判断文件是否存在,如果存在就读取数据

    if ([fm fileExistsAtPath:filePath])

    {

        NSLog(@"111111111");

        NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];

        return dict;

    } else {

        NSLog(@"2222222");

        NSMutableDictionary *emptyDict = [[[NSMutableDictionary alloc] init] autorelease];

        return emptyDict;

    }

}

2.归档(NSKeyedArchiver)

归档是iOS开发中数据存储常用的技巧,归档可以直接将对象储存成文件,把文件读取成对象。

相对于plist或者userdefault形式,归档可以存储的数据类型更加多样,并且可以存取自定义对象。对象归档的文件是保密的,在磁盘上无法查看文件中的内容,更加安全。

遵守NSCoding协议,并实现该协议中的两个方法。如果是继承,则子类一定要重写那两个方法。因为子类在存取的时候,会去子类中去找调用的方法,没找到那么它就去父类中找,所以最后保存和读取的时候新增加的属性会被忽略。需要先调用父类的方法,先初始化父类的,再初始化子类的。

保存数据的文件的后缀名可以随意命名。

demo

就像我们前面属性列表的介绍,归档(archiving)也是指另一种形式的序列化。但强大的一点是,它是任何对象都可以实现的更常规的储存数据类型;

在进行归档、解档的开发中,我们需要一起实现的,NSCoding,需要说明的是,标量(如int或float)以及大多数Foundation和Cocoa Touch类都遵循NSCoding协议(有例外,如UIImage不遵循),因此大多数类,还是比较容易实现归档操作的;

1、遵循NSCoding协议

NSCoding协议声明了2个方法:一个是将对象编码到归档中,另一个是对归档的解码来恢复我们之前归档的对象,使用方法与NSUserDefaults相似也可以用KVC对对象和原生数据类型(如int和float)进行编码和解码。

2、归档、解档

归档:创建一个NSKeyedArchiver实例,用于将对象归档到一个NSMutableData实例中,此时NSMutableData包含编码的数据,再使用键/码对需要的对象进行归档,最后告知完成,写入文件系统;

解档:也与归档对象步骤类似,创建一个NSData实例用于装载数据,并创建一个NSKeyedUnarchiver实例,对数据解码,然后使用先前用的键进行读取对象,最后告知程序解档完成;

如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,可以直接用NSKeyedArchiver进行归档和恢复

不是所有的对象都可以直接用这种方法进行归档,只有遵守了NSCoding协议的对象才可以

归档一个NSArray对象到Documents/array.archive

NSArray *array = [NSArray arrayWithObjects:@”a”,@”b”,nil];

[NSKeyedArchiver archiveRootObject:array toFile:path];

恢复(解码)NSArray对象

NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

归档Person对象

- (IBAction)save:(id)sender {

    // 存储自定义对象,可使用归档,但需要遵循NSCoding协议,实现对应方法

    Person* p = [[Person alloc] init];

    p.age = 18;

    p.name = @"zgq";

    // 获取caches文件夹

    NSString* cachesPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];

    NSString* path = [cachesPath stringByAppendingPathComponent:@"person.data"];

    NSLog(@"%@", cachesPath);

    // object: 需要归档的对象,任何对象都可以进行归档  file: 文件的全路径

    [NSKeyedArchiver archiveRootObject:p toFile:path];

}

- (IBAction)read:(id)sender {

    // 存进去是什么 就怎么取出

    Person* p = [NSKeyedUnarchiver unarchiveObjectWithFile:[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"person.data"]];

    NSLog(@"age: %ld , name: %@", p.age, p.name);

}

如果需要归档的类是某个自定义类的子类时,就需要在归档和解档之前先实现父类的归档和解档方法。即 [super encodeWithCoder:aCoder] 和 [super initWithCoder:aDecoder] 方法;

归档NSData

使用archiveRootObject:toFile:方法可以将一个对象直接写入到一个文件中,但有时候可能想将多个对象写入到同一个文件中,那么就要使用NSData来进行归档对象

NSData可以为一些数据提供临时存储空间,以便随后写入文件,或者存放从磁盘读取的文件内容。可以使用[NSMutableData data]创建可变数据空间

归档(编码)

// 新建一块可变数据区

NSMutableData *data = [NSMutableData data];

// 将数据区连接到一个NSKeyedArchiver对象

NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];

// 开始存档对象,存档的数据都会存储到NSMutableData中

[archiver encodeObject:person1 forKey:@"person1"];

[archiver encodeObject:person2 forKey:@"person2"];

// 存档完毕(一定要调用这个方法,调用了这个方法,archiver才会将encode的数据存储到NSMutableData中)

[archiver finishEncoding];

// 将存档的数据写入文件

[data writeToFile:path atomically:YES];

恢复(解码)

// 从文件中读取数据

NSData *data = [NSData dataWithContentsOfFile:path];

// 根据数据,解析成一个NSKeyedUnarchiver对象

NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];

Person *person1 = [unarchiver decodeObjectForKey:@"person1"];

Person *person2 = [unarchiver decodeObjectForKey:@"person2"];

// 恢复完毕(这个方法调用之后,unarchiver不能再decode对象,而且会通知unarchiver的代理调用unarchiverWillFinish:和unarchiverDidFinish:方法)

[unarchiver finishDecoding];

PS:也可将多个对象放入到一个数组中。

将数组进行归档,在数组对象执行archiveRootObject:toFile时,数组中每个对象会自动调用encodeWithCoder:方法进行归档;

相反数组文件进行解档时,在数组对象执行unarchiveObjectWithFile:时,数组中每个对象会自动调用initWithCoder:方法进行解档。

利用归档实现深复制

比如对一个Person对象进行深复制

// 临时存储person1的数据

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person1];

// 解析data,生成一个新的Person对象

Person *person2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];

// 分别打印内存地址

NSLog(@"person1:0x%x", person1); // person1:0x7177a60

NSLog(@"person2:0x%x", person2); // person2:0x7177cf0

四:CoreData、SQLite

适合储存数据量较大的数据,一般使用FMDB和CoreData来实现.

FMDB:

FMDB是iOS平台的SQLite数据库框架,FMDB以OC的方式封装了SQLite的C语言API,使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码,对比苹果自带的Core Data框架,更加轻量级和灵活,提供了多线程安全的数据库操作方法,有效地防止数据混乱。

CoreData:

Core Data是iOS5之后才出现的一个框架,它提供了对象-关系映射(ORM)的功能,即能够将OC对象转化成数据,保存在SQLite数据库文件中,也能够将保存在数据库中的数据还原成OC对象。在此数据操作期间,我们不需要编写任何SQL语句.但是直接操作CoreData显的不是那么容易,所以我多数的时候会使用MagicRecord来实现。MagicRecord是对CoreData的二次封装,使用起来简单操作方便.

文件(NSFileManager)

用来操作文件的类

NSFileManager *fm = [NSFileManager defaultManager];

// 判断文件是否存在,如果存在就读取数据

if ([fm fileExistsAtPath:filePath]) {

    NSMutableArray *array = [NSMutableArray arrayWithContentsOfFile:filePath];

    return array;

} else {

    NSMutableArray *emptyArray = [[[NSMutableArray alloc] init] autorelease];

    return emptyArray;

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容