數(shù)據(jù)持久化的本質(zhì)其實(shí)就是:將數(shù)據(jù)寫入文件保存起來。
關(guān)于沙盒
出于安全方面的考慮,iOS系統(tǒng)的沙盒機(jī)制規(guī)定每個(gè)應(yīng)用程序都只能訪問當(dāng)前沙盒目錄下的文件,而不能訪問其他的應(yīng)用程序的沙盒的內(nèi)容,對(duì)該應(yīng)用程序內(nèi)容起到保護(hù)作用。
每個(gè)應(yīng)用的沙盒有以下4個(gè)目錄:
- Documents: 用來存儲(chǔ)長(zhǎng)久保存的數(shù)據(jù)
- xxx.app: 應(yīng)用程序的包, 包含應(yīng)用程序加載所需的所有資源(readonly只讀, 不可修改), 平時(shí)使用的NSBundle就是該包
- Library:
- Caches: 本地緩存, 存儲(chǔ)想暫時(shí)保存的數(shù)據(jù)
- Preferences: 存儲(chǔ)用戶的偏好設(shè)置, 比如程序是否是第一次啟動(dòng)
- tmp: 保存各種的臨時(shí)文件
獲取上述各個(gè)目錄,首先得獲取根目錄
NSString *homePath = NSHomeDirectory();
獲取Documents
//獲取Documents文件夾目錄,第一個(gè)參數(shù)是說明獲取Documents文件夾目錄,第二個(gè)參數(shù)說明是在當(dāng)前應(yīng)用沙盒中獲取
NSArray *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *documentsPath = [docPath objectAtIndex:0];
獲取Cache目錄
NSArray *cacPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachePath = [cacPath objectAtIndex:0];
獲取temp目錄
NSString *tempPath = NSTemporaryDirectory();
對(duì)象歸檔
歸檔可以實(shí)現(xiàn)把對(duì)象存放在文件中,只要遵循了NSCoding協(xié)議的對(duì)象都可以通過它實(shí)現(xiàn)序列化。要實(shí)現(xiàn)NSCoding協(xié)議,需要下列兩個(gè)方法:
- -(void)encodeWithCoder:(NSCoder *)aCoder;
- -(instancetype)initWithCoder:(NSCoder *)aDecoder;
而對(duì)于對(duì)象是NSString,NSDictionary,NSArray,NSData,NSNumber等類型,可以直接使用NSKeyedArchiver進(jìn)行歸檔和解檔。
NSString *path = NSHomeDirectory();
NSString *archiverPath = [path stringByAppendingPathComponent:@"ftt.archiver"];
NSArray *array = @[@"abc",@"feg",@"opq"];
BOOL flag = [NSKeyedArchiver archiveRootObject:array toFile: archiverPath];
if (flag) {
NSLog(@"歸檔成功");
}
// 解檔(讀?。? NSArray *Arr = [NSKeyedUnarchiver unarchiveObjectWithFile:archiverPath];
NSLog(@"%@",Arr);
接下來實(shí)現(xiàn)想將一個(gè)自定義的對(duì)象保存到文件中,新建一個(gè)類FTPerson。
//.h文件中
#import <Foundation/Foundation.h>
// 如果想將一個(gè)自定義的對(duì)象保存到文件中必須實(shí)現(xiàn)NSCoding協(xié)議
@interface FTPerson : NSObject <NSCoding>
// 姓名
@property (nonatomic, copy)NSString *name;
// 年齡
@property (nonatomic,assign) NSInteger age;
// 身高
@property (nonatomic, assign) double height;
// 實(shí)現(xiàn)NSCoding協(xié)議中的兩個(gè)方法
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (instancetype)initWithCoder:(NSCoder *)aDecoder;
@end
接著在FTPerson.m文件中實(shí)現(xiàn)NSCoding協(xié)議中的兩個(gè)方法
#import "FTPerson.h"
@implementation FTPerson
// 當(dāng)將一個(gè)自定義的對(duì)象保存到文件的時(shí)候就會(huì)調(diào)用該方法
- (void)encodeWithCoder:(NSCoder *)aCoder {
NSLog(@"調(diào)用了encodeWithCoder: 方法");
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
[aCoder encodeDouble:self.height forKey:@"height"];
}
// 當(dāng)從文件中讀取一個(gè)對(duì)象的時(shí)候就會(huì)調(diào)用該方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
NSLog(@"調(diào)用了initWithCoder:方法");
// 注意:在構(gòu)造方法中需要先初始化父類的方法
if (self = [super init]) {
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
self.height = [aDecoder decodeDoubleForKey:@"height"];
}
return self;
}
@end
在viewController.m文件中導(dǎo)入FTPerson.h
- (void)saveData {
FTPerson *p = [[FTPerson alloc] init];
p.name = @"Tom";
p.age = 22;
p.height = 1.8;
// 獲取文件路徑
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)firstObject];
NSString *path = [docPath stringByAppendingPathComponent:@"person.fountain"];
NSLog(@"path = %@",path);
// 歸檔
[NSKeyedArchiver archiveRootObject:p toFile:path];
}
- (void)readData {
// 獲取文件路徑
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)firstObject];
NSString *path = [docPath stringByAppendingPathComponent:@"person.fountain"];
//解檔
FTPerson *p = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
NSLog(@"name = %@,age = %lu,height = %lf",p.name,p.age,p.height);
}
如果需要實(shí)現(xiàn)一個(gè)子類FTStudent,繼承自FTPerson,增加一個(gè)屬性weight。那么子類FTStudent一定要重寫
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (instancetype)initWithCoder:(NSCoder *)aDecoder;
這兩個(gè)方法,因?yàn)镕TPerson的子類在存取的時(shí)候,會(huì)去子類中去找調(diào)用的方法,沒找到那么它就去父類中找,所以最后保存和讀取的時(shí)候新增加的屬性就會(huì)被忽略。需要先調(diào)用父類的方法,即在encodewithCoder:方法中先加上一句[super encodeWithCoder:aCoder];
在initWithCoder:方法中加上一句self = [super initWithCoder:aDecoder];
先初始化父類的,再初始化子類的。