OC学习36:常用的数据存储方式

张建 lol

前言

iOS 开发过程中,经常需要 保存一些信息到本地 ,那么就需要数据存储。

沙盒(sandbox)

每个应用程序都有自己的沙盒,且只能访问自己的沙盒

  1. 如何访问沙盒文件呢?

通过Xcode查看

  • Window -> Devices And Simulators (快捷键 command + shift + 2)

  • 选中自己的设备,也就是iPhone手机,然后 Installed Apps 中选中需要查看的应用

  • 滑动鼠标到 Installed Apps 的底部,左键点击设置按钮,选择 Download Container,然后就可以将 沙盒文件 下载到本机上,查看所下载文件的内容即可

打开plist文件可查看设置

由上图可知 沙盒文件 下结构为:
应用程序包
1、Documents 持久化的数据
2、tmp 临时目录
3、Library
3.1、cache 缓存
3.2、Preference 偏好设置

  1. 沙盒文件下每个文件的具体作用?
  • Documents(保存程序生成的数据)

    • 保存由应用程序生成的文件或数据。
    • iTunes备份和恢复的时候会包括此目录。
    • 如果保存的网络下载的文件,上架审核的时候,会被拒
  • tmp(暂放数据,随时收回)

    • 保存临时文件,系统会自动回收,如:磁盘空间紧张或重启手机
    • iTunes不会备份和恢复此目录,此目录下文件可能会在 应用退出后删除
    • 程序员不需要管tmp文件下的释放
  • Library/Caches (保存网络下载的数据)

    • 缓存,保存从网络下载的文件,后续仍然需要继续使用,如:网络下载的离线数据,图片,视频等
    • 缓存目录中的文件不会自动删除,可以做离线访问
    • 程序必须提供一个完善的清除缓存目录的 解决方案
    • iTunes不会备份此目录,此目录下文件在 应用退出不会删除
  • Library/Preferences(偏好设置)

    • 系统偏好,用户偏好
    • 操作是通过 NSUserDefaults 实现的

沙盒路径的获取方法

  1. 沙盒的路径

NSHomeDirectory() 获取

1
2
3
4
5
6
// 获取沙盒下的根目录
NSString * sandboxPath = NSHomeDirectory();
NSLog(@"%@",sandboxPath);

======
/var/mobile/Containers/Data/Application/F8CF4000-F78B-4511-996D-819B96F64389
  1. Documents 路径
1
2
3
4
5
6
7
8
9
10
11
12
13
// 获取 Documents 路径
/*
NSSearchPathForDirectoriesInDomains 返回绝对路径
NSDocumentDirectory 表示获取沙盒的Documents目录
NSUserDomainMask 表示是用户主目录
YES/NO 表示是否获取全路径
*/
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * documentsPath = [paths lastObject];
NSLog(@"%@",documentsPath);

=======
/var/mobile/Containers/Data/Application/52A513F9-96E3-4DB0-9A40-BE3403E0BD34/Documents
  1. Caches 路径
1
2
3
4
5
6
7
// 获取Caches路径
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString * cachesPath = [paths lastObject];
NSLog(@"%@",cachesPath);

=======
/var/mobile/Containers/Data/Application/A0FF5812-EC9C-404F-BF63-4F60FFB1DE01/Library/Caches
  1. tmp 路径
1
2
3
4
5
6
// 获取tmp路径
NSString * tmpDir = NSTemporaryDirectory();
NSLog(@"%@",tmpDir);

=========
/private/var/mobile/Containers/Data/Application/206388C3-5180-4631-8120-3CB10EBDA0D8/tmp/

常用的存储方式

  • NSUserDefaults
  • Plist
  • 手动存放沙盒
  • NSKeyedArchiver归档 / NSKeyedUnarchiver解档
  • SQLite3
  • FMDB
  • CoreData
  • keyChain 钥匙串

NSUserDefaults

NSUserDefaults 可以存储 OC基本类型(如:字符串、数组、字典等)。但 不能存储自定义对象,如果是 自定义对象,需要进行归档操作。缓存数据到沙盒下 Library->PreferenceiTunes 同步设备时会 备份

  • 存入数据
1
2
[[NSUserDefaults standardUserDefaults] setObject:value forKey:key];
[[NSUserDefaults standardUserDefaults] synchronize];
  • 取出数据
1
[[NSUserDefaults standardUserDefaults] objectForKey:key];
  • 移除数据
1
2
[[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
[[NSUserDefaults standardUserDefaults] synchronize];

Plist

  • PlistXML属性列表 ,是将某些特定的类,通过XML文件的方式保存在 Documents 目录中

  • 优点:这种方式 好处是 可视化,可以 很直观 的看到文件的内容,同时 Xcode 提供了直接操作文件的功能。便于我们 增删改查

  • 缺点:plist 文件一般作为固态的数据形式保存,对于经常改动的数据不好操作

  1. 在项目中直接创建 Plist 文件。

注意:
命名的时候不能用Info.plist、INfo.plist、xxxInfo.plist等形式,否则会与系统中存在的Info.plist文件发生冲突。

点击Root这一行,然后通过点击右键->Add Row或者点击Root后面的加号来增加一行。
这一行中包含三个属性,key、type、value。其中key是字段属性,type是字段类型,value是字段对应的值。
Type包含7中类型,对写入的数据结构应属于7仲:
(NSString,NSData,NSDate,NSNumber,NSArray,NSDictionary)

  • 获取文件中的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
// 从plist中直接读取代码
NSString * filePath = [[NSBundle mainBundle] pathForResource:@"InfoPlist" ofType:@"plist"];
NSDictionary * dic = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSLog(@"%@",dic);

=====
2023-04-15 21:31:14.103498+0800 数据存储-plist[48688:9718159] {
Name = "\U5f20\U5efa";
Other = (
0,
1
);
}
  1. 代码创建 plist 文件,并 读写 数据,避免了在项目中创建 plist 文件导致不便更改的问题
  • ZJDataHelper.h
1
2
3
4
5
6
@interface ZJDataHelper : NSObject
// 保存用户信息
+ (BOOL)saveUserInfoInDocument:(NSDictionary *)dic;
// 读取用户信息
+ (NSMutableDictionary *)getUserInfoInDocument;
@end
  • ZJDataHelper.m 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#import "ZJDataHelper.h"

#define DOCMENT_USER_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) firstObject] stringByAppendingPathComponent:@"UserData.plist"]

@implementation ZJDataHelper
// 保存用户信息
+ (BOOL)saveUserInfoInDocument:(NSDictionary *)dic{
BOOL isSuccess;
NSString *document_path= DOCMENT_USER_PATH ;
NSURL *fileUrl = [NSURL fileURLWithPath:document_path];
isSuccess = [dic writeToURL:fileUrl atomically:YES];
return isSuccess;
}
// 读取用户信息
+ (NSMutableDictionary *)getUserInfoInDocument{
NSString *path= DOCMENT_USER_PATH;
NSMutableDictionary *dic = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
return dic;
}

@end

-(BOOL)writeToURL:(NSURL *)url atomically:(BOOL)atomically;
writeToURL 的好处是,既可以写入本地url也可以写入远程url,苹果推荐使用此方法写入plist文件

手动存放沙盒

沙盒和上面两种类似,只能存放 OC 基本数据,自定义对象不能直接存入。

1
2
3
4
5
6
7
8
// 假设我们需往cache 存入数据,并命名为test的txt格式文件中
NSString *filePath = [cachesPath stringByAppendingPathComponent:@"test.txt"];
NSArray *dic = [[NSArray alloc] initWithObjects:@"name",@"zj" ,nil];
if([dic writeToFile:filePath atomically:YES]){
NSLog(@"存入成功");
}
// 取出数据 打印
NSLog(@"%@",[NSArray arrayWithContentsOfFile:filePath]);

NSKeyedArchiver归档 / NSKeyedUnarchiver解档

之前说,不管是 NSUserDefaults 或者是 plist 都不能对 自定义的对象 进行存储, OC提供了 解归档 恰好解决这个问题。 解归档针对的是一个对象,假设我们现在有一个 Person 和 Student 的类,需要进行归档和解档。

  • 对象的 Person.h 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "Student.h"

@interface Person : NSObject<NSCoding>

//设置属性
@property (nonatomic,strong)UIImage * avatar;
@property (nonatomic,copy)NSString * name;
@property (nonatomic,assign)NSInteger age;
// 嵌套模型
@property (nonatomic,strong)Student * student;
@end
  • 对象的 Person.m 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#import "Person.h"

@implementation Person

//实现NSCoding协议方法
//解档
- (id)initWithCoder:(NSCoder *)aDecoder{

if (self = [super init]) {

self.avatar = [aDecoder decodeObjectForKey:@"avatar"];
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
// 嵌套模型
self.student = [aDecoder decodeObjectForKey:@"student"];
}
return self;
}
// 归档
- (void)encodeWithCoder:(NSCoder *)aCoder{

[aCoder encodeObject:self.avatar forKey:@"avatar"];
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
// 嵌套模型
[aCoder encodeObject:self.student forKey:@"student"];
}
@end
  • 对象的 Student.h 文件
1
2
3
@interface Student : NSObject<NSCopying>
@property (nonatomic,copy)NSString * name;
@end
  • 对象的 Student.m 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@implementation Student

//实现NSCoding协议方法
//解档
- (id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
self.name = [aDecoder decodeObjectForKey:@"name"];
}
return self;
}
//归档
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:self.name forKey:@"name"];
}
@end
  • 接下来只要使用 解/归 档辅助类就可以TestModel类进行解归档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 把对象归档是调用NSKeyedArchiver的工厂方法archiverRootObject:toFile方法
NSString * file = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString * path = [file stringByAppendingPathComponent:@"person.data"];

Person * person = [[Person alloc] init];
person.avatar = [UIImage imageNamed:@"123"];
person.name = @"张建";
person.age = 26;

// 嵌套模型
Student * student = [[Student alloc] init];
student.name = @"小J";
person.student = student;

[NSKeyedArchiver archiveRootObject:person toFile:path];

// 从文件中解档对象就是调用NSKeyedUnarchiver的一个工厂方法unarchiverObjectWithFile:即可
Person * person2 = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if (person2) {
UIImage * avatarImg = person2.avatar;
NSString * name = person2.name;
NSInteger age = person2.age;
Student * student2 = person2.student;
NSString * s_name = student2.name;
NSLog(@"avatar:%@ name:%@ age:%ld s_name:%@",avatarImg,name,age,s_name);
}
  • Post title:OC学习36:常用的数据存储方式
  • Post author:张建
  • Create time:2023-04-15 19:15:41
  • Post link:https://redefine.ohevan.com/2023/04/15/OC/OC学习36:常用的数据存储方式/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.