數據持久化的本質其實就是:將數據寫入文件保存起來。
關于沙盒
出于安全方面的考慮,iOS系統的沙盒機制規定每個應用程序都只能訪問當前沙盒目錄下的文件,而不能訪問其他的應用程序的沙盒的內容,對該應用程序內容起到保護作用。
每個應用的沙盒有以下4個目錄:
- Documents: 用來存儲長久保存的數據
- xxx.app: 應用程序的包, 包含應用程序加載所需的所有資源(readonly只讀, 不可修改), 平時使用的NSBundle就是該包
- Library:
- Caches: 本地緩存, 存儲想暫時保存的數據
- Preferences: 存儲用戶的偏好設置, 比如程序是否是第一次啟動
- tmp: 保存各種的臨時文件
獲取上述各個目錄,首先得獲取根目錄
NSString *homePath = NSHomeDirectory();
獲取Documents
//獲取Documents文件夾目錄,第一個參數是說明獲取Documents文件夾目錄,第二個參數說明是在當前應用沙盒中獲取
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();
對象歸檔
歸檔可以實現把對象存放在文件中,只要遵循了NSCoding協議的對象都可以通過它實現序列化。要實現NSCoding協議,需要下列兩個方法:
- -(void)encodeWithCoder:(NSCoder *)aCoder;
- -(instancetype)initWithCoder:(NSCoder *)aDecoder;
而對于對象是NSString,NSDictionary,NSArray,NSData,NSNumber等類型,可以直接使用NSKeyedArchiver進行歸檔和解檔。
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);
接下來實現想將一個自定義的對象保存到文件中,新建一個類FTPerson。
//.h文件中
#import <Foundation/Foundation.h>
// 如果想將一個自定義的對象保存到文件中必須實現NSCoding協議
@interface FTPerson : NSObject <NSCoding>
// 姓名
@property (nonatomic, copy)NSString *name;
// 年齡
@property (nonatomic,assign) NSInteger age;
// 身高
@property (nonatomic, assign) double height;
// 實現NSCoding協議中的兩個方法
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (instancetype)initWithCoder:(NSCoder *)aDecoder;
@end
接著在FTPerson.m文件中實現NSCoding協議中的兩個方法
#import "FTPerson.h"
@implementation FTPerson
// 當將一個自定義的對象保存到文件的時候就會調用該方法
- (void)encodeWithCoder:(NSCoder *)aCoder {
NSLog(@"調用了encodeWithCoder: 方法");
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
[aCoder encodeDouble:self.height forKey:@"height"];
}
// 當從文件中讀取一個對象的時候就會調用該方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
NSLog(@"調用了initWithCoder:方法");
// 注意:在構造方法中需要先初始化父類的方法
if (self = [super init]) {
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
self.height = [aDecoder decodeDoubleForKey:@"height"];
}
return self;
}
@end
在viewController.m文件中導入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);
}
如果需要實現一個子類FTStudent,繼承自FTPerson,增加一個屬性weight。那么子類FTStudent一定要重寫
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (instancetype)initWithCoder:(NSCoder *)aDecoder;
這兩個方法,因為FTPerson的子類在存取的時候,會去子類中去找調用的方法,沒找到那么它就去父類中找,所以最后保存和讀取的時候新增加的屬性就會被忽略。需要先調用父類的方法,即在encodewithCoder:方法中先加上一句[super encodeWithCoder:aCoder];
在initWithCoder:方法中加上一句self = [super initWithCoder:aDecoder];
先初始化父類的,再初始化子類的。