在實(shí)際的項(xiàng)目中,數(shù)據(jù)存儲(chǔ)是每個(gè)軟件開發(fā)者不可避免會(huì)涉及到的話題。對(duì)于iOS開發(fā)者而言,我們首先關(guān)心的就是app是把數(shù)據(jù)存儲(chǔ)在哪里的。
一.關(guān)于沙盒機(jī)制。
和Android系統(tǒng)不同的是,iOS系統(tǒng)使用的是特有的數(shù)據(jù)安全策略:沙盒機(jī)制。所謂沙盒機(jī)制是指:系統(tǒng)會(huì)為每個(gè)APP分配一塊獨(dú)立的存儲(chǔ)空間,用于存儲(chǔ)圖像,圖標(biāo),聲音,映像,屬性列表,文本等文件,并且在默認(rèn)情況下每個(gè)APP只能訪問自己的空間。
怎么查看模擬器中APP的沙盒的目錄結(jié)構(gòu)?
在程序中執(zhí)行如下語句,得到沙盒的路徑:
NSLog(@"%@",NSHomeDirectory());
打印的結(jié)果是:
/Users/XXX/Library/Developer/CoreSimulator/Devices/41D208F9-4E93-48EC-A254-10EA246048DF/data/Containers/Data/Application/8AD61414-4125-408A-B0FF-DE451973E2C8
在Finder中,F(xiàn)inder>前往>前往文件夾。將上面打印出的路徑張貼到輸入框內(nèi),然后點(diǎn)擊前往。就會(huì)出現(xiàn)如下內(nèi)容:

我們可以看到三個(gè)文件夾:Documents, Library, tmp。
- Documents: 用于保存應(yīng)用運(yùn)行時(shí)生成的需要持久化、非常大的或者需要頻繁更新的數(shù)據(jù),iTunes會(huì)自動(dòng)備份該目錄 .
//獲取目錄位置的代碼如下
NSArray *documentDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
//上面的documentDirectory是只有一個(gè)元素的數(shù)組,還需要以下代碼取出路徑
NSString *myDocPath = [documentDirectory objectAtIndex:0];
//或者 NSString *myDocPath = [documentDirectory lastObject]; - Libaray: 用于存儲(chǔ)程序的默認(rèn)設(shè)置和其他狀態(tài)信息,iTunes會(huì)自動(dòng)備份該目錄。Libaray/下主要有兩個(gè)文件夾:Libaray/Caches和Libaray/Preferences。
//獲取目錄位置的代碼如下:
NSArray *libraryDirectory = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask,YES);
NSString * libraryDirectory = [libraryDirectory lastObject];
- Libaray/Caches:存放緩存文件,iTunes不會(huì)備份此目錄,此目錄下文件不會(huì)在應(yīng)用退出刪除,一般存放體積比較大,不是很重要的資源。
//獲取目錄位置的代碼如下:
NSArray *cachesDirectory = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES);
NSString *cachesDirectory = [cachesDirectory lastObject];
- Libaray/Preferences:保存應(yīng)用的所有偏好設(shè)置,ios的Settings(設(shè)置)應(yīng)用會(huì)在該目錄中查找應(yīng)用的設(shè)置信息,iTunes會(huì)自動(dòng)備份該目錄。
//獲取目錄位置的代碼如下:
NSArray * preferencesDirectory = NSSearchPathForDirectoriesInDomains(NSPreferencesDirectory,NSUserDomainMask,YES);
NSString * preferencesDirectory = [preferencesDirectory lastObject]; - tmp: 保存應(yīng)用運(yùn)行時(shí)所需的臨時(shí)數(shù)據(jù),使用完畢后再將相應(yīng)的文件從該目錄刪除,應(yīng)用沒有運(yùn)行時(shí),系統(tǒng)也可能會(huì)自動(dòng)清理該目錄下的文件,iTunes不會(huì)同步該目錄,iPhone重啟時(shí)該目錄下的文件會(huì)丟失。
//獲取目錄位置的代碼如下:
NSString *tmp = NSTemporaryDirectory();
既然存的地方清楚了,我們接下來聊聊iOS常見的存儲(chǔ)方式:
二.常見的存儲(chǔ)方式。
我們常見的有四種存儲(chǔ)方式:用NSUserDefaults存儲(chǔ)配置信息 ,用NSKeyedArchive歸檔的形式來保存對(duì)象數(shù)據(jù), 文件沙盒存儲(chǔ) , 數(shù)據(jù)庫(kù)存儲(chǔ) 。
1. 用NSUserDefaults存儲(chǔ)配置信息
該方法被設(shè)計(jì)用來存儲(chǔ)設(shè)備和應(yīng)用的配置、屬性、用戶的信息,它通過一個(gè)工廠方法返回默認(rèn)的實(shí)例對(duì)象。它實(shí)際上是存儲(chǔ)于文件沙盒中的一個(gè).plist文件,并且沒有被系統(tǒng)加密,只是ios6以后不是存于常用的文檔目錄下,所以不破解系統(tǒng)是看不到。
該文件的可以存儲(chǔ)的數(shù)據(jù)類型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。如果要存儲(chǔ)其他類型,則需要轉(zhuǎn)換為前面的類型,才能用NSUserDefaults存儲(chǔ)。
用該方式存數(shù)據(jù)的代碼如下 :
NSString *teleNum = @"1868XX23763";
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject: teleNum forKey:@"teleNum"];
用該方式取數(shù)據(jù)的代碼如下 :
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSString *teleNum = [userDefaults objectForKey:@"teleNum"]];
2. 文件沙盒存儲(chǔ)
主要存儲(chǔ)非機(jī)密數(shù)據(jù),大的數(shù)據(jù),如圖片。
存文件的操作步驟如下:
(1). 獲得文件即將保存的路徑
方法一:
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);
NSString *ourDocumentPath =[documentPaths objectAtIndex:0];
方法二:
NSString *sandboxPath = NSHomeDirectory();
NSString *documentPath = [sandboxPath
stringByAppendingPathComponent:@"Documents"];
(2). 生成在該路徑下的文件
NSString *FileName=[documentDirectory stringByAppendingPathComponent:fileName];//fileName就是保存文件的文件名
(3). 往文件中寫入數(shù)據(jù)
[data writeToFile:FileName atomically:YES];//將NSData類型對(duì)象data寫入文件,文件名為FileName
從沙盒中取出文件:
取就比較簡(jiǎn)單,只需下面一行代碼!
NSData data=[NSData dataWithContentsOfFile:FileName options:0 error:NULL];//從FileName中讀取出數(shù)據(jù)
3. 用NSKeyedArchive歸檔的形式來保存對(duì)象數(shù)據(jù)
由于筆者暫時(shí)沒有用過數(shù)據(jù)庫(kù)存儲(chǔ),所以就不在這里幫門弄斧了。如文章題目,今天主要想談的是第三種方法,即用NSKeyedArchive歸檔。下面將用一個(gè)簡(jiǎn)單的項(xiàng)目來講解。
使用情景:在我們的實(shí)際開發(fā)過程中,為了更好的用戶體驗(yàn),可能需要在APP上存儲(chǔ)用戶的某些信息,比如淘寶APP會(huì)記錄用戶曾經(jīng)填寫過的收貨地址。這種數(shù)據(jù)不方便用前面提到的NSUserDefaults來存儲(chǔ):一是,因?yàn)榈刂窋?shù)據(jù)是一個(gè)模型,NSUserDefaults只能用于NSString、NSArray、NSDictionary等常用的數(shù)據(jù)類型;二是,用戶還會(huì)經(jīng)常對(duì)它進(jìn)行增刪改等操作,NSUserDefaults無法滿足。
此時(shí),用NSKeyedArchive歸檔的形式來存儲(chǔ)地址模型數(shù)據(jù)最符合要求。

(1)創(chuàng)建地址模型CLVoiceApplyAddressModel
CLVoiceApplyAddressModel.h文件
#import <Foundation/Foundation.h>
@interface CLVoiceApplyAddressModel : NSObject<NSCoding>
//用于存儲(chǔ)多個(gè)地址時(shí),標(biāo)記用戶選中的狀態(tài)
@property (nonatomic, copy) NSString *state;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *phoneNum;
@property (nonatomic ,copy) NSString *mainAddress;
@property (nonatomic ,copy) NSString *detailAddress;
+(instancetype)AddressModelWithDict:(NSDictionary *)dict;
-(instancetype)initAddressModelWithDict:(NSDictionary *)dict;
@end
CLVoiceApplyAddressModel.m文件
#import "CLVoiceApplyAddressModel.h"
@implementation CLVoiceApplyAddressModel
-(void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:_state forKey:@"state"];
[aCoder encodeObject:_name forKey:@"name"];
[aCoder encodeObject:_phoneNum forKey:@"phoneNum"];
[aCoder encodeObject:_mainAddress forKey:@"mainAddress"];
[aCoder encodeObject:_detailAddress forKey:@"detailAddress"];
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
self = [super init];
if (self) {
self.state= [aDecoder decodeObjectForKey:@"state"];
self.name = [aDecoder decodeObjectForKey:@"name"];
self.phoneNum = [aDecoder decodeObjectForKey:@"phoneNum"];
self.mainAddress = [aDecoder decodeObjectForKey:@"mainAddress"];
self.detailAddress = [aDecoder decodeObjectForKey:@"detailAddress"];
}else{
return nil;
}
return self;
}
+(instancetype)AddressModelWithDict:(NSDictionary *)dict{
return [[self alloc] initAddressModelWithDict:dict];
}
-(instancetype)initAddressModelWithDict:(NSDictionary *)dict{
if (self = [super init]) {
self.state =[dict objectForKey:@"state"];
self.name =[dict objectForKey:@"name"];
self.phoneNum =[dict objectForKey:@"phoneNum"];
self.mainAddress =[dict objectForKey:@"mainAddress"];
self.detailAddress =[dict objectForKey:@"detailAddress"];
}
return self;
}
@end
為了實(shí)現(xiàn)對(duì)歸檔地址的操作,我們專門創(chuàng)建了一個(gè)工具類:CLInvoiceApplyAddressModelTool。
#import <Foundation/Foundation.h>
@class CLVoiceApplyAddressModel;
@interface CLInvoiceApplyAddressModelTool : NSObject
+(NSArray *)allAddressInfo;
+(CLVoiceApplyAddressModel *)currentSelectedAddress;
+(void)update;
+(void)updateAddressInfoAfterDeleted;
+(void)setSelectedAddressByNewInfoArray:(NSArray *)infoArray;
+(void)addInfo:(CLVoiceApplyAddressModel *)info;
+(void)removeInfoAtIndex:(NSUInteger)index;
+(void)updateInfoAtIndex:(NSUInteger)index withInfo:(CLVoiceApplyAddressModel *)info;
+(void)removeAllInfo;
@end
#import "CLInvoiceApplyAddressModelTool.h"
#import "CLVoiceApplyAddressModel.h"
#define AddressInfosPath [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"addressInfo1.data"]
@implementation CLInvoiceApplyAddressModelTool
static NSMutableArray *_addressInfos;
+(NSArray *)allAddressInfo{
_addressInfos = [NSKeyedUnarchiver unarchiveObjectWithFile:AddressInfosPath];
if (!_addressInfos) _addressInfos = [NSMutableArray array];
return _addressInfos;
}
+(CLVoiceApplyAddressModel *)currentSelectedAddress{
CLVoiceApplyAddressModel *currentAddress;
BOOL hasSelectedAddress = NO;
if ([self allAddressInfo].count) {
for (CLVoiceApplyAddressModel *info in _addressInfos) {
if ([info.state isEqualToString:@"1"]) {
currentAddress = info;
hasSelectedAddress = YES;
break;
};
}
}else if([self allAddressInfo].count == 0 || hasSelectedAddress)
{
currentAddress = nil;
}
return currentAddress;
}
+(void)update{
[NSKeyedArchiver archiveRootObject:_addressInfos toFile:AddressInfosPath];
}
+(void)updateAddressInfoAfterDeleted{
if (_addressInfos.count) {
if (![self currentSelectedAddress]) {
CLVoiceApplyAddressModel *info = [CLInvoiceApplyAddressModelTool allAddressInfo][0];
info.state = @"1";
[CLInvoiceApplyAddressModelTool updateInfoAtIndex:0 withInfo:info];
}
}
}
+(void)setSelectedAddressByNewInfoArray:(NSArray *)infoArray{
[NSKeyedArchiver archiveRootObject:infoArray toFile:AddressInfosPath];
}
+(void)addInfo:(CLVoiceApplyAddressModel *)info{
if (!_addressInfos.count) {
_addressInfos = [NSMutableArray array];
}
for (CLVoiceApplyAddressModel *oldInfo in _addressInfos ) {
oldInfo.state = @"0";
}
[_addressInfos insertObject:info atIndex:0];
[self update];
}
+ (void)removeInfoAtIndex:(NSUInteger)index {
[_addressInfos removeObjectAtIndex:index];
[self update];
}
+ (void)removeAllInfo{
[_addressInfos removeAllObjects];
[self update];
}
+ (void)updateInfoAtIndex:(NSUInteger)index withInfo:(CLVoiceApplyAddressModel *)info {
[_addressInfos replaceObjectAtIndex:index withObject:info];
[self update];
}
@end