前面已经讲解了SQLite,FMDB以及CoreData的基本操作和代码讲解(CoreData也在不断学习中,上篇博客也会不断更新中)。本篇我们将讲述在实际开发中,所使用的iOS数据持久化的方式以及怎么会使用到这些方式,都会以本人实际开发的场景为例,大约需要花10-15分钟,欢迎大家指正。
一、前言
和大家说一个真实故事,前年我去美图面试(当时的技术仅仅是UI和接口的实现,并不注重很多底层实现和很多概念的原理,换句话说,就是真正的码农),过了技术第一轮和第二轮(前两年的也就是问问技术点的实现),到了第三轮的时候,应该是项目经理面试我,其中一个题目问住了我,因为iOS我是自学的,也算是不断摸索的。
“请你说一下iOS 一般会默认自己程序的目录,请说一下这个目录机构,以及组成部分,里面包含什么?”
当时的情景就是,我是谁,我在哪,大家也可以想一下!!!
其实那个项目经理问的就是我们经常听到的沙盒文件,其中iOS在程序默认下只会访问自己的程序目录-也就是沙盒文件。
1. 寻找沙盒文件位置
大家可能不知道怎么查看正在真机(或者模拟器)运行下的沙盒文件,可以按照下面的步骤查看
1.1 第一步,打开项目,选择windows(我是以真机为例,模拟器亦是)
1.2 第二步,进入界面,选择自己的app,点击绿色按钮,
1.3 第三步,选择第二个Download Container,导出到指定的文件夹(我是导出到桌面)
1.4 最后,我们发现桌面有个后缀名为.xcappdata的文件夹,我是导入到桌面。
2.沙盒文件目录结构
2.1 在1.4中后缀.xcappdata,右击选择->显示包内容,打开看到目录如下图,也可以继续点击查看更详细内容。
2.2目录讲解
:->AppData:也就是应用程序包,存的是程序的源文件,其中里面有可执行的文件以及资源文件。通过以下可以获得路径
NSString *path = [[NSBundle mainBundle] bundlePath];
NSLog(@"%@", path);
AppData里面包括了以下内容:
(1)Documents
也是我们经常使用到的目录,我们经常看到iTunes同步,同步的就是此文件中的内容,Documents适合存比较重要的数据。我们可以通过以下代码拿到Documents
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"%@", path);
(2)Library
Library比较适合存储比较大的数据,并且iTunes不会同步此文件,也不会备份。可以通过以下拿到路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"%@", path);
(2.1)获取Caches目录路径
NSString *cachesDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
NSLog(@"Caches目录路径是%@",docDir);
(2.2)Preferences
主要包括了程序中的偏好设置文件,下面会讲述这个Preferences
(3)SystemData
目前暂不讲解这个目录(里面目录为空)
(4)tmp
用于存储临时文件,主要保存程序再次启动中不需要的信息数据,并且该路径中的文件并不会被itunes同步和备份。通过以下获得
NSString *tmpDir = NSTemporaryDirectory();
上面就是前言部分,大家这篇博客,我们主要讲述iOS数据持久化操作,好,我们正式以项目的形式讲述iOS的数据持久化操作。
二、iOS数据持久化操作
对于iOS的数据持久化操作,在网上也是有很多讲解,本篇主要讲述本人项目中使用到的方式进行讲解。iOS持久化操作方式主要有以下五种方式:
1. plist文件方式,也就是属性列表
2. preference 也就是偏好设置
3. NSKeyedArchiver 也就是归档
4. SQLite
5. CoreData
下面我们就一一讲解五种方式,以项目为例。
2.1 plist (属性列表)
plist是将比较特定的类(不经常改变的类),通过XML的方式存储在目录中。
自己项目有个分享的功能,因为分享渠道是固定的以及界面内容不会改变,所以自己当初采取plist文件的方式进行存储。效果如下:
具体分享平台自己使用plist文件进行存储,下面是自己plist文件
点开plist文件,如下图
右击该plist文件->选择Open As ->选择Source Code,如下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>data</key>
<array>
<dict>
<key>titleLabel</key>
<string>微信好友</string>
<key>imageView</key>
<string>cart_wx</string>
</dict>
<dict>
<key>titleLabel</key>
<string>朋友圈</string>
<key>imageView</key>
<string>cart_friend</string>
</dict>
<dict>
<key>titleLabel</key>
<string>QQ好友</string>
<key>imageView</key>
<string>cart_qq</string>
</dict>
<dict>
<key>titleLabel</key>
<string>QQ空间</string>
<key>imageView</key>
<string>cart_que</string>
</dict>
</array>
</dict>
</plist>
下面讲述怎么使用分享plist文件。
通过上图和XML文件可以看出最外面是字典,我在项目是使用懒加载方式加载这个字典
代码如下:
- (NSDictionary *)configDatas{
if (!_configDatas) {
NSString *path = [[NSBundle mainBundle]pathForResource:@"IOACartShareDatas" ofType:@".plist"];
NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:path];
_configDatas = dic;
}
return _configDatas;
}
在分享的view内进行传值
然后进入shareView中查看具体赋值,布局采用了UICollectionView布局
补充一下:
自己上面是一个一个在plist里面创建的元素,也可以通过writeToFile存储以及arrayWithContentsOfFile取文件。
(1)writeToFile存储方式
NSArray *array = @[@"2", @"1", @"3"];
[array writeToFile:fileName atomically:YES];
(2)arrayWithContentsOfFile读取方式
NSArray *result = [NSArray arrayWithContentsOfFile:fileName];
NSLog(@"%@", result);
2.2 Preference偏好设置
Preference偏好设置一般用存储应用程序的配置信息文件,最好不要在Preference中存储其他数据,Preference通过操作NSUserDefaults完成操作。
2.2.1 获得NSUserDefaults文件
//获得NSUserDefaults文件
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
2.2.2 开始向文件中写入内容
[userDefaults setObject:@"哈哈哈" forKey:@"a"];
[userDefaults setBool:YES forKey:@"success"];
[userDefaults setInteger:21 forKey:@"age"];
2.2.3 确定是否同步。确定是否调用synchronize方法,想要写入文件时,就应该调用[userDefaults synchronize]方法
如果同步
[userDefaults synchronize];
反之,不写。
2.2.4 在合适的地方调取文件
NSString *name = [userDefaults objectForKey:@"a"];
BOOL sex = [userDefaults boolForKey:@"success"];
NSInteger age = [userDefaults integerForKey:@"age"];
NSLog(@"%@, %d, %ld", name, sex, age);
Preference会把所有的数据存储到一个文件中,也就是在上面preference文件下的plist文件。
2.3 NSKeyedArchiver 归档
在使用归档NSKeyedArchiver一定要遵守NSCoding协议,也就是遵守了NSCoding协议的对象,我们都可以通过归档NSKeyedArchiver进行序列化。
通过查看NSCoding协议文件,发现如下
NSCoding协议文件声明了两个文件,上述图中,也是必须要实现的。encodeWithCoder用于将对象编码到归档中,而initWithCoder用于通过解档来获取一个新对象。
需要注意的是:
假如要归档的类恰好是一个自定义类的子类时候,就应该要在归档和解档之前要首先实现父类的方法,也就是必须要实现以下两个方法:[super encodeWithCoder:aCoder]和[super initWithCoder:aDecoder]
拓展->
NSCopying协议文件如下(有一次面试官问我,NSCopying与NSMutableCopying),大家当增长一个知识点吧!
下面以自己以前的项目为准,讲述归档的使用(因为以前项目较小,又是独立开发,采用NSKeyedArchiver存储信息)。
2.3.1 使用NSKeyedArchiver归档存储用户登录部分信息,注销登录清除。定义了一个类AppUserDefaults
点开AppUserDefaults.h文件定义登录获取的属性(仅仅展示部分属性,实现都一样)
通过以下方法对登录信息的赋值
我们看一下归档和解档,以及上面方法的实现(注解上面type属性是代表是不同短登录:例如医生端和患者端)
2.3.2 encodeWithCoder归档(显示部分属性,否则界面太大)
2.3.2 initWithCoder解档
2.3.2 登录通过setSessionWithLoginName设置属性
+ (void)setSessionWithLoginName:(NSString *)loginName
version:(NSString *)version
loginTime:(long long)loginTime
id_:(long)id_
userType:(int)userType
name:(NSString *)name
phone:(NSString *)phone
pic:(NSString *)pic
companyName:(NSString *)companyName
address:(NSString *)address
type:(NSInteger)type
cityName:(NSString *)cityName
cityId:(long)cityId
latitude:(NSString *)latitude
longitude:(NSString *)longitude {
//判断版本号,通过session
AppUserDefaults *session = [AppUserDefaults getSession];
if (session && ![session.version isEqualToString:@""]) {
if (type != -1)
session.type = type;
if (loginTime != -1)
session.loginTime = loginTime;
if ([loginName isNotBlank])
session.loginName = loginName;
if ([version isNotBlank])
session.version = version;
if (id_ != -1)
session.id_ = id_;
}
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:session];
if (data) {
NSUserDefaults *user = [NSUserDefaults standardUserDefaults];
[user setObject:data forKey:@"session"];
[user synchronize];
}
}
+ (AppUserDefaults *)getSession {
AppUserDefaults *session = nil;
NSUserDefaults *user = [NSUserDefaults standardUserDefaults];
NSData *data = [user objectForKey:@"session"];
if (data) {
session = [NSKeyedUnarchiver unarchiveObjectWithData:data];
if (session && [session.version isEqualToString:@""]) {
session = nil;
}
}
return session;
}
2.3.4 上面就是AppUserDefaults文件内容,下面讲述在登录时候进行赋值。
首先要懒加载AppUserDefaults
- (AppUserDefaults *)userDefaults{
if (_userDefaults == nil) {
_userDefaults = [AppUserDefaults getSession];
}
return _userDefaults;
}
登录成功时赋值
2.3.5 如果用户注销之后或者退出应用应该给重新清空,如下:
总结:
使用NSKeyedArchiver的工厂方法[NSKeyedArchiver archiveRootObject:obj toFile:path];方法。demo如下:
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"student.data"];
Person *person = [[Person alloc] init];
person.avatar = self.avatarView.image;
person.name = self.nameField.text;
person.age = [self.ageField.text integerValue];
[NSKeyedArchiver archiveRootObject:person toFile:file];
如果想解档需要调用[NSKeyedUnarchiver unarchiveObjectWithFile:path];方法如下
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"student.data"];
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
if (person) {
self.avatarView.image = person.avatar;
self.nameField.text = person.name;
self.ageField.text = [NSString stringWithFormat:@"%ld", person.age];
}
在这之前还是要定义属性和实现协议方法
//1.遵循NSCoding协议
@interface student : NSObject //2.设置属性
@property (strong, nonatomic) UIImage *avatar;
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) NSInteger age;
@end
//解档
- (id)initWithCoder:(NSCoder *)aDecoder {
if ([super init]) {
self.avatar = [aDecoder decodeObjectForKey:@"avatar"];
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
//归档
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.avatar forKey:@"avatar"];
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
}
2.4 SQLite
以下并不是推广博客,主要是很多内容,没有必要写第二遍,即使写,也没用专门一篇详细。
关于SQLite讲解,请查看以前博客
SQLite的使用还是很麻烦的,在一般的项目开发中,会使用FMDB操作,这个也有讲解,
对于SQLite和FMDB的使用区别,看这篇博客希望对大家有所帮助
2.5 CoreData
CoreData目前自己所从事的工作还没有使用过CoreData,但自己通过尝试demo,还是有所感悟,关于CoreData的理解,自己也写了一个博客,
以上就是iOS数据存储的5中方式,都是自己真实项目所使用的,希望对大家有所帮助 !!!如有错误的地方请告诉我,大家共同进步。