iOS中的永久存储,也就是在关机从新启动设备,或者关闭应用时,不会丢失数据。在实际开发应用时,每每须要持久存储数据的,这样用户才能在对应用进行操做后,再次启动能看到本身更改的结果与痕迹。iOS开发中,咱们须要数据持久化这一种技术,也须要不断在实际开发的工做与学习中完善数据持久化这一开发技术。html
【本次开发环境: Xcode:7.2 iOS Simulator:iphone6S plus By:啊左】 ios
(本节2个项目demo的下载:属性列表Demo、对象的归档解档Demo)git
本文将介绍4种数据持久化的方法:github
一、属性列表数据库
二、对象的归档、解档编程
三、数据库 SQLite3 的运用数组
四、Core Data 的运用网络
固然,iOS开发中,持久化数据的方法不局限于以上这4种方法,还可使用啊左的博客:ios开发--应用设置及用户默认设置【一、bundle的运用】这篇博客介绍的应用设置的存储方法;app
也可使用传统的C语言I/O调用(好比:fopen() )的读取与写入数据,可使用Cocoa的底层文件管理工具,只不过这两种方法都须要开发者写入不少代码,本文不做介绍,若是须要的话,读者能够上网找一下。iphone
在介绍4种持久化存储方式前,咱们须要先介绍3个有关的文件夹,以及沙盒机制:
什么是沙盒机制?
咱们手中的iphone/ipad设备上包含着闪存(flash memory),它的功能和一个硬盘功能等价。当设备断电后数据依然可以被保存下来,应用程序能够把数据文件保存到山村上,而且读取它们。可是,须要注意的是,咱们所开发的应用程序是没法访问整个闪存的,由于闪存上面会专门有一部分给咱们,这一部分就是属于咱们开发的整个应用程序的沙盒(sandbox)了。iOS系统下,每一个应用都只能看到本身的沙盒,这就防止对其余应用程序的数据文件进行读写活动。就像咱们的应用程序也可以看见一些系统拥有的高级别目录,可是却没法进行任何的写入操做;
那么,如何获取属于本身的目录?
一、获取Documents目录
因为iOS中应用的数据存储是沙盒机制,所以读取和写入文件,咱们须要调用C函数 “NSSearchPathForDirectoriesInDomains()”来查找各类目录,(这个C函数能够基于Mac OS X平台的Cocoa共享)
如检索Documents目录路径的代码:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *pathDirectory = [paths objectAtIndex:0];
//或者NSString *pathDirectory = [paths lastObject];
第一个常量NSDocumentDirectory表示正在查找沙盒Document目录的路径(若是参数为NSCachesDirectory则表示沙盒Cache目录),第二个常量NSUserDomainMask代表咱们但愿将搜索限制在应用的沙盒内;(在Mac OS X中,此常量表示咱们但愿该函数查看用户的主目录,所以才会有这个命名;)
返回的是一个数据paths,为何位于索引0就是咱们须要的Documents目录?由于每个应用只有一个Documents目录,所以只有一个目录符合这个条件;
接下来,咱们能够为刚才检索到的目录pathDirectory的结尾加一个字符串来建立一个文件名,以下:
NSString *filename = [pathDirectory stringByAppendingPathComponent:@"data.txt"];
//注意是stringByAppendingPathComponent,不要拼错。
这个时候咱们获得的filename字符串就能够进行建立、读取、写入文件了。
二、获取tmp目录:
能够用NSTemporaryDirectory()的Foundation函数返回一个字符串,该字符串包含到应用临时目录的完整路径。 同上,在结尾附上文件名就能够建立指向该目录下的文件路径了。
NSString *tmpPath = NSTemporaryDirectory(); NSString *temFile = [tmpPath stringByAppendingPathComponent:@"tempFile.txt"];
-------------------------------------
下面介绍数据持久化方法的具体实现:
1、属性列表
在【一、bundle的运用】中,咱们使用了属性列表来指定应用的默认设置与相应的数据存储,而且方便使用Xcode或者Property List Editor应用手动编辑它们,只要字典或者数据包含特定可序列化对象,就能够NSDictionary和NSArray实例写入属性列表或者从属性列表建立相应的对象;
什么是序列化对象?
序列化对象(Serialized objects),是指能够被转换为字节流以便于存储到文件中或者经过网络进行传输的对象;
虽说任何对象均可以被序列化,可是只有某些特定的对象才能放置到某个集合类(例如:NSArray、 NSMutableArray、NSDictionary、 NSData等)中,并使用该集合类的方法在属性列表存储中使用,其余的对象也可使用归档的方法进行存储(在对象的归档、解档咱们会进行详细介绍)。
那咱们开始构建第一个使用属性列表存储数据的简单应用:
具体的功能效果如【图1】,可让用户在4个文本框中输入数据,应用退出时会把这些字段保存到属性列表中,并在下次启动时重现加载恢复上次的数据;
【图1 效果图】
在Xcode中,使用Single View Application模板建立一个新项目,命名为persistence1。
在“Main.storyboard”中拖入4个标签、4个文本框控件,拖动并对齐标签与文本框,并依次修改标签文本如【图1】,“ViewController.h”中添加一个装载4个文本框的数组“lineFields”:
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController @property (strong,nonatomic)IBOutletCollection(UITextField)NSArray *lineFields; @end
打开辅助编辑器,经过control键将4个文本框链接到 lineFields 这个数组,确保链接顺序为从顶部到底部!
在项目导航面板中,点击"ViewController.m" ,将如下代码添加到 @implementation与 @end 的中间,这个方法在后面会一直调用:
//获取属性列表路径中数据文件的完整路径 dataFilepath //须要加载和保存数据的代码均可以调用该方法.
-(NSString *)dataFilepath { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *pathDirectory = [paths objectAtIndex:0]; return [pathDirectory stringByAppendingPathComponent:@"data.txt"]; }
接下来,在viewDidload中添加代码,并添加相应的响应器方法:
- (void)viewDidLoad { [super viewDidLoad]; NSString *filePath = [self dataFilepath]; //判断是否存在属性列表文件
if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { //存在,则把数据赋值给文本框
NSArray *ar= [[NSArray alloc]initWithContentsOfFile:filePath]; for(int i =0;i<4;i++) { UITextField *textField = self.lineFields[i]; textField.text = ar[i]; } } //若是应用进入后台:
UIApplication *app = [UIApplication sharedApplication]; [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(applicationWillResignActiveNotification:) name:UIApplicationWillResignActiveNotification object:app]; } //应用进入后台时执行:
-(void)applicationWillResignActiveNotification:(NSNotification *)notification { NSString *pathFile = [self dataFilepath]; //咱们不是用迭代数组的形式,而是用了便捷的方法,使用NSarrry类中的valueForKey方法,把lineFields中包含@“text”值的数组赋值给array.
NSArray *array = [self.lineFields valueForKey:@"text"]; //把字符串数组写入文件。
[array writeToFile:pathFile atomically:YES]; }
这段代码的意思是,首先检查完整路径下的数据文件是否存在,不存在的话就不加载了;
若存在,则把数组中的对象复制到4个文本框中,根据刚才咱们建立数组“lineFields”的时候,与文本框的链接顺序,就能够把数据赋值给文本框了。
而后在应用终止或者进入后台以前进行数据的保存处理,因此咱们使用通知中心,订阅了名为 “UIApplicationWillResignActiveNotification” 的通知,并在后面实现了“applicationWillResignActiveNotification”这个方法。
当用户按下手机的“Home键”,或者其余事件发生(好比来电)致使应用进入后台的状况,便调用此方法,把字符串数组写入咱们建立的属性列表文件里面。
好了,咱们已经完成了GUI界面的基础设计以及代码的编程了,接下来,按下“command+R”运行它;
若是没有其余问题的话,咱们能够分别键入4个文本框,而后点击Home键(也就是command+shift+H)、双击Home键(按住command+shift,双击H),或者在Xcode中终止应用退出模拟器(至关于手机重启),以验证数据在应用获得永久保存了。
总结:属性列表的序列化很实用,也相对比较简单,可是也会有点限制,就是只能将一小部分对象保存在属性列表中,接下来咱们介绍下强大的归档解档对象的数据储存方法;
2、对模型对象进行归档、解档
就像咱们前面属性列表的介绍,归档(archiving)也是指另外一种形式的序列化。但强大的一点是,它是任何对象均可以实现的更常规的储存数据类型;
在进行归档、解档的开发中,咱们须要一块儿实现的,还有NSCoding和NSCopying协议,须要说明的是,标量(如int或float)以及大多数Foundation和Cocoa Touch类都遵循NSCoding协议(有例外,如UIImage不遵循),所以大多数类,仍是比较容易实现归档操做的;
一、遵循NSCoding协议、NSCopying协议
NSCoding协议声明了2个方法:一个是将对象编码到归档中,另外一个是对归档的解码来恢复咱们以前归档的对象,使用方法与NSUserDefaults类似也能够用KVC对对象和原生数据类型(如int和float)进行编码和解码。
NSCopying协议用于容许复制对象,使得使用数据模型对象时具有较大的灵活性;
二、归档、解档
归档:建立一个NSKeyedArchiver实例,用于将对象归档到一个NSMutableData实例中,此时NSMutableData包含编码的数据,再使用键/码对须要的对象进行归档,最后告知完成,写入文件系统;
解档:也与归档对象步骤相似,建立一个NSData实例用于装载数据,并建立一个NSKeyedUnarchiver实例,对数据解码,而后使用先前用的键进行读取对象,最后告知程序解档完成;
这样说有点干,囧~ 仍是上代码吧:
a."linePesist"类的建立
在Xcode中,使用Single View Application模板建立一个新项目,命名为persistence2,没错,仍是跟属性列表同样的应用模板。
可是须要建立一个新文件,按command+N,或者从File菜单中依次选择New->New File。出现新建文件向导后,选择Cocoa Touch,而后选择Objective-C class,单击Next,将类命名为“linePesist”,并在“Subclass of”一栏中选择NSObject,单击Next,再单击Create。该类作为咱们的数据模型,而且将用于存储属性列表应用的字典中的数据。
单击“linePesist.h”,修改代码以下:
#import <Foundation/Foundation.h>
//遵循NSCoding、NSCopying协议
@interface linePesist : NSObject<NSCoding,NSCopying> @property (nonatomic,copy)NSArray *array; @end
这是一个拥有数组类型的简单数据模型,数组能够用于咱们放置文本框的数据字段。
接下来,咱们进行“linePesist.m”的编辑:
#import "linePesist.h"
#define CodeStr @"CodeStr" //用于归档解档的时候用的键名
@implementation linePesist /* 经过遵循NSCoding和NSCoping中的方法,建立可归档的数据对象。 */
#pragma mark -- Coding
//编码
-(void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.array forKey:CodeStr]; } //解码
-(id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; if(self) { self.array = [aDecoder decodeObjectForKey:CodeStr]; } return self; } #pragma mark -- Coping
-(id)copyWithZone:(NSZone *)zone { linePesist *copy = [[[self class]allocWithZone:zone] init]; NSMutableArray *muAr = [[NSMutableArray alloc]init]; for(id line in self.array) { [muAr addObject:[line copyWithZone:zone]]; } copy.array = muAr; return copy; } @end
用预约义的“CodeStr”作为编码解码的键,存储4个文本框的字符串,而后用一样的“CodeStr”键进行解码,将4个字符串复制到copyWithZone建立的linePesist对象中;
b.“ViewController”类实现
建立可归档的数据对象以后,咱们即可以使用此来进行持久化的存储。点击“ViewController.m”编辑界面,并进行如下除划掉的部分的代码编辑。
#import "ViewController.h"
#import "linePesist.h" //导入数据模型类
#define CodeString @"CodeString"
@implementation ViewController -(NSString *)dataFile { NSArray *ar = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES); NSString *fielpath = [ar objectAtIndex:0]; return [pathDirectory stringByAppendingPathComponent:@"data.txt"]; return [fielpath stringByAppendingPathComponent:@"data.archive"]; //改修后缀名,以避免与属性列表建立的文件重复,而加载成旧的的文件。 不用查字典了。。archive表归档
} - (void)viewDidLoad { [super viewDidLoad]; NSString *filepath = [self dataFile]; NSLog(@"%@",filepath); if([[NSFileManager defaultManager]fileExistsAtPath:filepath]) { NSArray *ar= [[NSArray alloc]initWithContentsOfFile:filePath]; for(int i =0;i<4;i++) { UITextField *textField = self.lineFields[i]; textField.text = ar[i]; } //建立2个实例
NSData *data = [[NSData alloc]initWithContentsOfFile:filepath]; NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:data]; //把已归档的对象读取。赋值给linepesist
linePesist *linepesist =[unarchiver decodeObjectForKey:CodeString]; [unarchiver finishDecoding]; //完成解档
for(int i = 0;i<4;i++) { //把解档的数据分别赋值给文本框
UITextField *textField = self.fourLines[i]; textField.text = linepesist.array[i]; //记得是.text
} } UIApplication *app = [UIApplication sharedApplication]; [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(applicationWillResignActiveNotification:) name:UIApplicationWillResignActiveNotification object:app]; } // 应用回到后台,数据归档、写入文件中
-(void)applicationWillResignActiveNotification:(NSNotification*)notfication { NSArray *array = [self.lineFields valueForKey:@"text"]; [array writeToFile:pathFile atomically:YES]; NSString *pathField = [self dataFile]; linePesist *linepesit = [[linePesist alloc]init]; linepesit.array = [self.fourLines valueForKey:@"text"]; //建立2个实例
NSMutableData *data = [[NSMutableData alloc]init]; NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data]; //使用键/值编码对但愿包含在归档中的对象进行归档。
[archiver encodeObject:linepesit forKey:CodeString]; [archiver finishEncoding]; //与属性列表同样,须要在最后写入文件,由于属性列表与归档都是一种序列化,最后仍须要写入文件。
[data writeToFile:pathField atomically:YES]; } @end
除了存储数据的方式不同,GUI界面与上一个版本的一致。运行这个版本的persistence应用。效果应该也与咱们运行属性列表时的同样。
好了,属性列表、归档解档对象的存储方法咱们介绍到这里,读者能够对比下有什么不一样,呃...最明显的应该是归档的代码量多些,可是相应的,归档会具备很是好的伸缩性,至少从代码上面看,是这样的。
在下一节,咱们将介绍另外2种方法:数据库 SQLite3 的运用、Core Data 的运用;