移動開發(fā)何乎,數(shù)據(jù)存儲是一定而且經常遇到的昔搂,存儲的數(shù)據(jù)有大有小玲销,大數(shù)據(jù)和小數(shù)據(jù)的存儲方式也不一樣,存儲數(shù)據(jù)也要考慮性能摘符,針對iOS的數(shù)據(jù)存儲贤斜,有以下幾種存儲方式。
常見的存儲方式
- plist 格式文件存儲
- NSUserDefaults 沙盒存儲(個人偏好設置)
- 文件讀寫存儲
- 解歸檔存儲
- 數(shù)據(jù)庫存儲
了解緩存逛裤,先要了解iOS中沙盒機制這個概念
沙盒其實質就是在iOS系統(tǒng)下瘩绒,每個應用在內存中對應的存儲空間。
每個iOS應用都有自己的應用沙盒(文件系統(tǒng)目錄)别凹,與其他文件系統(tǒng)隔離草讶,各個沙盒之間相互獨立,而且不能相互訪問(手機沒有越獄情況下)炉菲,各個應用程序的沙盒相互獨立的堕战,在系統(tǒng)內存消耗過高時坤溃,系統(tǒng)會收到內存警告并自動退出軟件。這就保證了系統(tǒng)的數(shù)據(jù)的安全性及系統(tǒng)的穩(wěn)定性嘱丢。
一個沙盒目錄如下:
Documents 應用程序在運行時生成的一些需要長久保存的數(shù)據(jù)薪介。
Library/Caches 儲存應用程序網絡請求的數(shù)據(jù)信息(音視頻與圖片等的緩存)。此目錄下的數(shù)據(jù)不會自動刪除越驻,需要程序員手動清除該目錄下的數(shù)據(jù)汁政。主要用于保存應用在運行時生成的需要長期使用的數(shù)據(jù).一般用于存儲體積較大數(shù)據(jù)。
Library/Preferences 設置應用的一些功能會在該目錄中查找相應設置的信息,該目錄由系統(tǒng)自動管理,通常用來儲存一些基本的應用配置信息,比如賬號密碼,自動登錄等缀旁。
tmp 保存應用運行時產生的一些臨時數(shù)據(jù);應用程序退出记劈、系統(tǒng)空間不夠、手機重啟等情況下都會自動清除該目錄的數(shù)據(jù)并巍。無需程序員手動清除該目錄中的數(shù)據(jù)目木。
plist 格式文件存儲
- plist文件 即為屬性列表文件
- 可以存儲的類型有NSString,NSDictionary,NSArray,NSNumber,Boolean,NSDate,NSData等基本類型
- 常用于存儲用戶的設置,或存儲項目中經常用到又不經常改變的數(shù)據(jù)
- 創(chuàng)建.plist可以用xcode工具懊渡,也可以用代碼
- 不適合存儲大量數(shù)據(jù)刽射,而且只能存儲基本類型
- 可以實現(xiàn):增,刪剃执,改誓禁,查等操作,但數(shù)據(jù)的存取是一次性的全部操作肾档,所以性能方向表現(xiàn)并不好
- NSDate,BOOL,Int,Float,NSNumber,NSData的數(shù)據(jù)存儲都是轉換成NSDictionary的Key-Value形式之后摹恰,通過NSDictionary存儲方式存儲的。
字符串存儲:
NSArray *paths = NSSearchPathForDirectoriesInDomains(searchPath, NSUserDomainMask, YES);
NSString *filePath = [[paths firstObject] stringByAppendingPathComponent:@"test.plist"];
BOOL isSave = [string writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"存儲是否成功---%d",isSave);
NSString *testString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
NSLog(@"存儲的字符串----%@",testString);
NSArray存儲
NSArray *paths = NSSearchPathForDirectoriesInDomains(searchPath, NSUserDomainMask, YES);
NSString *filePath = [[paths firstObject] stringByAppendingPathComponent:@"test.plist"];
[array writeToFile:filePath atomically:YES];
NSArray *mArray = [NSArray arrayWithContentsOfFile:filePath];
NSLog(@"存儲的Array-----%@",mArray);
NSDictionary存儲
NSArray *paths = NSSearchPathForDirectoriesInDomains(searchPath, NSUserDomainMask, YES);
NSString *filePath = [[paths firstObject] stringByAppendingPathComponent:@"test.plist"];
[dic writeToFile:filePath atomically:YES];
NSDictionary *mDic = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSLog(@"存儲的Dic------%@",mDic);
NSUserDefaults 沙盒存儲(個人偏好設置)
- NSUserDefaults 沙盒存儲(個人偏好存儲) 是個單例類阁最,用于存儲少量數(shù)據(jù)戒祠,例如登錄后的用戶名骇两,密碼等速种。
- 應用程序啟動后,會在沙盒路徑Library -> Preferences 下默認生成以工程bundle為名的.plist文件低千,用NSUserDefaults存儲的數(shù)據(jù)都是存儲在該.plist文件中配阵。
- 這種方式本質是操作plist文件,所以性能方面的考慮同plist文件數(shù)據(jù)儲存
[[NSUserDefaults standardUserDefaults] setBool:bol forKey:key]; //BOOL存儲
[[NSUserDefaults standardUserDefaults] setInteger:i forKey:key]; //NSInteger存儲
[[NSUserDefaults standardUserDefaults] setObject:obj forKey:key]; //Object存儲
[[NSUserDefaults standardUserDefaults] setURL:url forKey:key]; //NSURL存儲
[[NSUserDefaults standardUserDefaults] setFloat:f forKey:key]; //Float存儲
[[NSUserDefaults standardUserDefaults] setDouble:d forKey:key]; //Double存儲
[[NSUserDefaults standardUserDefaults] URLForKey:key]; //URL的數(shù)據(jù)獲取
[[NSUserDefaults standardUserDefaults] objectForKey:key]; //Object或者基本類型的獲取
文件讀寫存儲(NSFileManager)
- 文件操作可通過單例 NSFileManager 處理示血。文件存儲的路徑可以代碼設置棋傍。
- 可以存儲大量數(shù)據(jù),對數(shù)據(jù)格式沒有限制难审。
- 但由于數(shù)據(jù)的存取必須是一次性全部操作瘫拣,所以在頻繁操作數(shù)據(jù)方面性能欠缺。
創(chuàng)建文件夾
/**
創(chuàng)建文件夾
@param dirName 文件夾名稱
@param dirPath 文件夾所在路徑
@return 創(chuàng)建結果YES/NO
*/
+ (BOOL)creatDir:(NSString *)dirName dirPath:(NSString *)dirPath {
dirPath = [dirPath stringByAppendingPathComponent:dirName];
if ([FileManager fileExistsAtPath:dirPath]) {
NSLog(@"創(chuàng)建失敗告喊,目錄已存在");
} else {
BOOL isCreat = [FileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
if (isCreat) {
NSLog(@"創(chuàng)建成功");
return YES;
} else {
NSLog(@"創(chuàng)建失敗,請檢查路徑");
return NO;
}
}
return NO;
}
創(chuàng)建文件
/**
創(chuàng)建文件
@param fileName 文件名稱
@param dirPath 文件所在的文件夾路徑
@return 創(chuàng)建結果YES/NO
*/
+ (BOOL)creatFile:(NSString *)fileName dirPath:(NSString *)dirPath {
NSString *filePath = [dirPath stringByAppendingPathComponent:fileName];
BOOL isDir = NO;
BOOL isFileExist = [FileManager fileExistsAtPath:filePath isDirectory:&isDir];
//目錄是否存在
if (!(isFileExist && isDir)) {
BOOL isCreat = [FileManager createFileAtPath:filePath contents:nil attributes:nil];
if (isCreat) {
NSLog(@"創(chuàng)建成功");
return YES;
} else {
NSLog(@"創(chuàng)建失敗");
return NO;
}
} else {
NSLog(@"創(chuàng)建失敗麸拄,文件已存在");
return NO;
}
return NO;
}
String寫入
/**
String寫入
@param content 寫入的字符串內容
@param filePath 寫入文件的路徑
@return 寫入結果YES/NO
*/
+ (BOOL)writeString:(NSString *)content filePath:(NSString *)filePath {
BOOL isFileExist = [FileManager fileExistsAtPath:filePath];
if (isFileExist) {
BOOL isWrite = [content writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
if (isWrite) {
NSLog(@"寫入成功");
return YES;
} else {
NSLog(@"寫入失敗");
return NO;
}
} else {
NSLog(@"寫入失敗,文件不存在");
return NO;
}
return NO;
}
Array寫入
/**
Array寫入
@param array 要寫入的array
@param filePath 文件路徑
@return 寫入結果YES/NO
*/
+ (BOOL)writeArray:(NSArray *)array filePath:(NSString *)filePath {
BOOL isFileExist = [FileManager fileExistsAtPath:filePath];
if (isFileExist) {
BOOL isCreat = [array writeToFile:filePath atomically:YES];
if (isCreat) {
NSLog(@"寫入成功");
return YES;
} else {
NSLog(@"寫入失敗");
return NO;
}
} else {
NSLog(@"寫入失敗,文件不存在");
return NO;
}
return NO;
}
Dictionary寫入
/**
Dictionary寫入
@param dic 要寫入的dictionary
@param filePath 文件路徑
@return 寫入結果YES/NO
*/
+ (BOOL)writeDictionary:(NSDictionary *)dic filePath:(NSString *)filePath {
BOOL isFileExist = [FileManager fileExistsAtPath:filePath];
if (isFileExist) {
BOOL isCreat = [dic writeToFile:filePath atomically:YES];
if (isCreat) {
NSLog(@"寫入成功");
return YES;
} else {
NSLog(@"寫入失敗");
return NO;
}
} else {
NSLog(@"寫入失敗,文件不存在");
return NO;
}
return NO;
}
讀取存儲的String
/**
讀取存儲的String
@param filePath 文件路徑
@return 存儲的字符串
*/
+ (NSString *)readFileWithFilePath:(NSString *)filePath {
NSString *str = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
return str;
}
讀取存儲的array
/**
讀取存儲的array
@param filePath 文件路徑
@return 存儲的array
*/
+ (NSArray *)readArrayWithFilePath:(NSString *)filePath {
NSArray *array = [NSArray arrayWithContentsOfFile:filePath];
return array;
}
讀取存儲的dictionary
/**
讀取存儲的dictionary
@param filePath 文件路徑
@return 存儲的dictionary
*/
+ (NSDictionary *)readDictionaryWithFilePath:(NSString *)filePath {
NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:filePath];
return dic;
}
讀取文件夾中所有文件
/**
讀取文件夾中所有文件
@param dirPath 文件夾路徑
@return 文件夾中所有的文件
*/
+ (NSArray *)readAllFileWithDirPath:(NSString *)dirPath {
NSArray *array = [FileManager contentsOfDirectoryAtPath:dirPath error:nil];
return array;
}
判斷文件是否存在
/**
判斷文件是否存在
@param filePath 文件路徑
@return 文件是否存在
*/
+ (BOOL)fileIsExistFilePath:(NSString *)filePath {
BOOL isFileExist = [FileManager fileExistsAtPath:filePath];
return isFileExist;
}
文件大小
/**
文件大小
@param filePath 文件路徑
@return 文件大小
*/
+ (unsigned long long)computerFileSizeWithFilePath:(NSString *)filePath {
BOOL isDir;
BOOL isFile = [FileManager fileExistsAtPath:filePath isDirectory:&isDir];
if (!isDir) {
if (!isFile) {
NSLog(@"文件不存在");
return 0;
}else {
unsigned long long fileSize = [FileManager attributesOfItemAtPath:filePath error:nil].fileSize;
return fileSize;
}
} else {
NSLog( @"該文件是一個目錄");
return 0;
}
return 0;
}
文件夾中的所有文件大小
/**
文件夾中的所有文件大小
@param dirPath 文件夾路徑
@return 所有文件大小
*/
+ (unsigned long long)computerDirSizeWithDirPath:(NSString *)dirPath {
BOOL isExist = [FileManager fileExistsAtPath:dirPath];
if (isExist) {
NSEnumerator *childFilesEnumerator = [[FileManager subpathsAtPath:dirPath] objectEnumerator];
NSString* fileName;
long long folderSize = 0;
while ((fileName = [childFilesEnumerator nextObject]) != nil){
NSString* fileAbsolutePath = [dirPath stringByAppendingPathComponent:fileName];
folderSize += [FileManager attributesOfItemAtPath:fileAbsolutePath error:nil].fileSize;
}
return folderSize;
} else {
NSLog(@"目錄不存在");
return 0;
}
return 0;
}
移除文件
/**
移除文件
@param filePath 文件路徑
@return 移除結果YES/NO
*/
+ (BOOL)removeFileWithFilePath:(NSString *)filePath {
if ([FileManager fileExistsAtPath:filePath]) {
BOOL isRemove = [FileManager removeItemAtPath:filePath error:nil];
if (!isRemove) {
NSLog(@"移除失敗");
return NO;
} else {
NSLog(@"移除成功");
return YES;
}
} else {
NSLog(@"文件不存在");
return NO;
}
return NO;
}
移動文件
/**
移動文件
@param filePath 要移動的文件路徑
@param toDirPath 文件要移動到的文件夾路徑
@param newFileName 移動文件的新名稱
@return 是否移動成功
*/
+ (BOOL)moveFileWithFilePath:(NSString *)filePath toDirPath:(NSString *)toDirPath newFileName:(NSString *)newFileName {
NSString *des = [toDirPath stringByAppendingPathComponent:newFileName];
if (![FileManager fileExistsAtPath:filePath]) {
NSLog(@"文件不存在");
return NO;
}else {
if (![FileManager fileExistsAtPath:toDirPath]) {
NSLog(@"目標路徑不存在");
return NO;
} else {
BOOL move = [FileManager moveItemAtPath:filePath toPath:des error:nil];
if (move) {
NSLog( @"移動成功");
return YES;
} else {
NSLog(@"移動失敗");
return NO;
}
}
}
return NO;
}
解歸檔存儲
- plist 與 NSUserDefaults(個人偏好設置)兩種類型的儲存只適用于系統(tǒng)自帶的一些常用類型派昧,而且前者必須拿到文件路徑,后者也只能儲存應用的主要信息拢切。
- 對于開發(fā)中自定義的數(shù)據(jù)模型的儲存蒂萎,我們可以考慮使用歸檔儲存方案。
- 歸檔保存數(shù)據(jù)淮椰,文件格式自己可以任意五慈,沒有要求 ; 即便設置為常用的數(shù)據(jù)格式(如:.c .txt .plist 等)要么不能打開,要么打開之后亂碼顯示主穗。
- 值得注意的是使用歸檔保存的自定義模型需要實現(xiàn)NSCoding協(xié)議下的兩個方法泻拦。
- 不適合存儲大量數(shù)據(jù),可以存儲自定義的數(shù)據(jù)模型忽媒。
- 雖然歸檔可以存儲自定義的數(shù)據(jù)結構聪轿,但在大批量處理數(shù)據(jù)時,性能上仍有所欠缺猾浦。
YJArchiveModel.h
@interface YJArchiveModel : NSObject <NSCoding>
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) BOOL sex;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) double price;
@end
YJArchiveModel.m
@implementation YJArchiveModel
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:_name forKey:@"name"];
[aCoder encodeInteger:_age forKey:@"age"];
[aCoder encodeBool:_sex forKey:@"sex"];
[aCoder encodeDouble:_price forKey:@"price"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
_name = [aDecoder decodeObjectForKey:@"name"];
_age = [aDecoder decodeIntegerForKey:@"age"];
_sex = [aDecoder decodeBoolForKey:@"sex"];
_price = [aDecoder decodeDoubleForKey:@"price"];
}
return self;
}
@end
數(shù)據(jù)庫存儲
- SQLite : 它是一款輕型的嵌入式數(shù)據(jù)庫陆错,安卓和ios開發(fā)使用的都是SQLite數(shù)據(jù)庫;占用資源非常的低金赦,在嵌入式設備中音瓷,可能只需要幾百K的內存就夠了;而且它的處理速度比Mysql夹抗、PostgreSQL這兩款著名的數(shù)據(jù)庫都還快绳慎。
- FMDB 正是基于 SQLite 開發(fā)的一套開源庫。使用時漠烧,需要自己寫一些簡單的SQLite語句
- CoreData 是蘋果給出的一套基于 SQLite 的數(shù)據(jù)存儲方案杏愤;而且不需要自己寫任何SQLite語句。該功能依賴于 CoreData.framework 框架已脓,該框架已經很好地將數(shù)據(jù)庫表和字段封裝成了對象和屬性珊楼,表之間的一對多、多對多關系則封裝成了對象之間的包含關系
- Core Data的強大之處就在于這種關系可以在一個對象更新時度液,其關聯(lián)的對象也會隨著更新厕宗,相當于你更新一張表的時候,其關聯(lián)的其他表也會隨著更新堕担。Core Data的另外一個特點就是提供了更簡單的性能管理機制已慢,僅提供幾個類就可以管理整個數(shù)據(jù)庫。由于直接使用蘋果提供的CoreData容易出錯霹购,這里提供一個很好的三方庫 MagicalRecord
緩存系統(tǒng)
對大多數(shù) APP 而言佑惠,都是 Hybrid 開發(fā),Web 頁與原生同時存在,其中 Web 頁可能是 UIWeb 也可能是 WKWeb 膜楷。所以與之相應的緩存系統(tǒng)乍丈,應該包括 Web 緩存與 原生接口數(shù)據(jù)緩存兩部分。
原生接口部分的數(shù)據(jù)緩存
存儲方式:主要采用文件讀寫把将、歸檔轻专、個人偏好設置(NSUserDefaults) 。
具體說明:大部分接口數(shù)據(jù)解析之后寫入文件保存(讀寫操作最好 GCD 子線程操作)察蹲;整個應用需要用到的重要數(shù)據(jù)模型可以考慮采用歸檔方式(標記狀態(tài)的數(shù)據(jù)模型);與用戶相關的信息请垛、單個標記標識等采用個人偏好設置。
補充: 原生接口數(shù)據(jù)存儲方式以上三種方式就已夠用洽议;當然對于一些涉及查詢宗收、刪除、更新等操作的數(shù)據(jù)模型亚兄,就需要使用數(shù)據(jù)庫操作混稽。這里推薦使用 CoreData 的封裝庫 MagicalRecord 。
關于存儲的使用审胚,我寫了一個簡單的demol匈勋,除了數(shù)據(jù)庫的,其他的存儲基本上都有膳叨,有興趣的可以看一看:YJDataStore