学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

iOS本地数据存储

前言

据存储本质就是运行时的对象保存在文件、数据库中。数据存储可以分为两步:首先是将对象转换成二进制数据,这一步也叫序列化;相反,将二进制数据转换成对象则称为反序列化;然后是考虑二进制数据如何保存和读取。

沙盒目录

iOS系统为每个App分配了独立的数据目录,App只能对自己的目录进行操作,这个目录所在被称为沙盒目录。
一个应用的沙盒包括下面三个部分:应用目录、沙盒目录、iCloud目录。

Documents目录用于保存App的数据,包括App运行时需要的各类文件以及用户的数据等。Documents文件夹可以在连接iTunes时选择备份,通常Documents目录用来存放可以对外的文件。

Library目录用来保存不对外的数据,但同样可以被iTunes备份(Library/Caches目录除外,原因就和目录名一样,里面应该只放Caches)。Library/Caches目录用来放置运行时产生的临时文件以及缓存文件,空间不足时可能会被iOS系统删除。Library/Preferences目录通常用于保存用户的设置等信息,比如我们常用的NSUserDefaults类就会以plist的方式保存在该目录中

tmp目录用来保存不重要的临时文件,在系统重启后会被清空,容易知道这个也不会被iTunes备份。

1
2
3
4
5
6
7
8
9
10
// 获取沙盒根目录路径
NSString *homeDir = NSHomeDirectory();
// 获取Documents目录路径
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) firstObject];
//获取Library的目录路径
NSString *libDir = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask,YES) lastObject];
// 获取cache目录路径
NSString *cachesDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES) firstObject];
// 获取tmp目录路径
NSString *tmpDir =NSTemporaryDirectory();

我们工程中的图片资源是不是放在沙盒目录中呢?
工程中的资源文件在NSBundle,而NSBundle会被打包到.ipa文件上传到App Store,而用户安装App时候,会把App放置在应用目录(非沙盒目录)。

NSBundle

在用NSFileManager去读取文件的时候需要提供文件路径,但是有时候我们并不知道资源被放置在哪个目录,此时可以用到NSBundle。

在Xcode编译运行的时候,会把Xcode内的图片、xib、音频等都拷贝到.app文件中。
NSBundle就是系统提供,用来读取这些资源的类

NSBundle * mainBundle = [NSBundle mainBundle];
这样我们就拿到我们的mainBundle,通过mainBundle我们可以查找对应的资源:
NSString *path =[mainBundle pathForImageResource:@”some_pic_name”]; // 查找图片地
也可以通过mainBundle直接加载xib:
[[NSBundle mainBundle] loadNibNamed:@"SSProgressView" owner:self options:nil];

通过CocoaPods安装的Pod库,要如何读取其资源?

1
2
NSString *path = [[NSBundle mainBundle] pathForResource:@"SSTestPod" ofType:@"bundle"];
NSBundle *podBundle = [NSBundle bundleWithPath:path];

Keychain

保存在沙盒目录的数据是不安全的,用户可能会导出沙盒数据进行分析。
有没有什么保存方式是更安全的呢?
iOS给出的答案是keychain。

keychain是iOS提供给App存储敏感和安全相关数据用的工具。keychain同样会被iTunes备份,即使App重装仍能读取到上次保存的结果。为了保证数据安全,keychain内的数据都是经过加密。

1
2
3
4
5
6
7
8
9
import <Security/Security.h>
// SELECT
OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result);
// ADD
OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result);
// UPDATE
OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate);
// DELETE
OSStatus SecItemDelete(CFDictionaryRef query);

KeychainWrapper为例,来看看封装后更简单的接口。

1
2
3
4
5
6
- (void)savePassword:(NSString *)password;
- (BOOL)deleteItem;

- (NSString *)readPassword;
//返回当前accessGroup下的service的所有Keychain Item
+ (NSArray *)passwordItemsForService:(NSString *)service accessGroup:(NSString *)accessGroup;

具体的使用样例:

1
2
3
4
5
6
KeychainWrapper *wrapper = [[KeychainWrapper alloc] initWithSevice:kKeychainService account:self.account accessGroup:kKeychainAccessGroup];
NSString *saveStr = [wrapper readPassword];
if (!saveStr) {
[wrapper savePassword:@"test_password"];
}
NSLog(@"saveStr:%@", saveStr);

要保存在keychain,即使应用卸载重装,仍旧能读取到该值。

对象序列化

NSCoding是系统提供的序列化协议,在对象转换为二进制的时候,会通过NSCoding的方法回调开发者。

1
2
3
4
@protocol NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder; // NS_DESIGNATED_INITIALIZER
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@interface SSUser : NSObject <NSCoding>

@property (nonatomic, assign) NSInteger gender;
@property (nonatomic, strong) NSString *userName;


- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
self.gender = [[aDecoder decodeObjectForKey:@"gender"] integerValue];
self.userName = [aDecoder decodeObjectForKey:@"userName"];
return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:@(self.gender) forKey:@"gender"];
[aCoder encodeObject:self.userName forKey:@"userName"];
}
@end

YYModel具有几大特点:
1、利用iOS的Runtime特点,无需继承;
2、安全转换数据类型,常见Crash都进行了保护;
3、扩展性强,提供多种容器扩展;