所謂的持久化,就是將數(shù)據(jù)保存到硬盤中,使得在應(yīng)用程序或機(jī)器重啟后可以繼續(xù)訪問(wèn)之前保存的數(shù)據(jù)。在iOS開(kāi)發(fā)中,有很多數(shù)據(jù)持久化的方案.
在開(kāi)發(fā)中常用的存儲(chǔ)方案主要有以下幾種:
1 plist文件(屬性列表)
2.NSKeyedArchiver(歸檔)
3.SQLite 3
4.CoreData(這個(gè)不經(jīng)常用)
5.keyChain
沙盒
在介紹各種存儲(chǔ)方法之前,有必要說(shuō)明以下沙盒機(jī)制。iOS程序默認(rèn)情況下只能訪問(wèn)程序自己的目錄,這個(gè)目錄被稱為“沙盒”。
沙盒的目錄結(jié)構(gòu)如下圖:

Documents文件夾。存儲(chǔ)用戶數(shù)據(jù)和用戶文檔。
Library文件夾。存儲(chǔ)系統(tǒng)數(shù)據(jù)。
tmp文件夾。臨時(shí)文件夾,用于存儲(chǔ)臨時(shí)數(shù)據(jù),這個(gè)文件夾不穩(wěn)定,數(shù)據(jù)可能被清除。
SystemData 最近剛加的,存儲(chǔ)系統(tǒng)數(shù)據(jù)
1.1存儲(chǔ)為plist文件
plist文件是將某些特定的類,通過(guò)XML文件的方式保存在目錄中。
可以被序列化的類型只有如下幾種:必須要遵循
NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;
eg:存入字符串
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/myFile.plist"];
NSError *err = nil;
//writeToFile把字符串存入文件。第一個(gè)參數(shù)表示存儲(chǔ)的路徑(包含文件名),第二個(gè)參數(shù)表示是否原子性寫入(如果多個(gè)線程可能同時(shí)操作這個(gè)文件,原子性需要寫YES),第三個(gè)參數(shù)表示文本的編碼方式(字符串存儲(chǔ)到硬盤上必須先轉(zhuǎn)換為二進(jìn)制數(shù)據(jù),選擇按照哪種編碼格式進(jìn)行編碼)。第四個(gè)參數(shù)表示如果存儲(chǔ)失敗,失敗的原因。
if (![@"天涯明月新,朝暮最相思" writeToFile:path atomically:NO encoding:NSUTF8StringEncoding error:&err]) {
NSLog(@"%@",err);
}
讀取字符串
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/myFile.plist"];
//從文件中讀取字符串,第一個(gè)參數(shù),讀取的文件的路徑,第二個(gè)參數(shù)編碼方式,第三個(gè)參數(shù),如果失敗,失敗的原因。
NSString *text = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
NSLog(@"%@",text);
eg:存入數(shù)組
NSArray *array = @[@"惟將長(zhǎng)夜未開(kāi)眼",@"報(bào)答平生未展眉"];
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/array.plist"];
//把數(shù)組存入文件
[array writeToFile:path atomically:NO];
讀取數(shù)組
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/array.plist"];
//從文件中讀取數(shù)組
NSArray *array = [NSArray arrayWithContentsOfFile:path];
for (NSString *strs in array)
{
NSLog(@"%@",strs);
}
1.2 NSUserDefaults 適用于輕量級(jí)數(shù)據(jù)了比較小的,一般存儲(chǔ)用戶信息的相關(guān)數(shù)據(jù)。
//1.獲得NSUserDefaults文件
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
//2.向文件中寫入內(nèi)容
[userDefaults setObject:@"AAA" forKey:@"a"];
[userDefaults setBool:YES forKey:@"sex"];
[userDefaults setInteger:21 forKey:@"age"];
//2.1立即同步
[userDefaults synchronize];
//3.讀取文件
NSString *name = [userDefaults objectForKey:@"a"];
BOOL sex = [userDefaults boolForKey:@"sex"];
NSInteger age = [userDefaults integerForKey:@"age"];
NSLog(@"%@, %d, %ld", name, sex, age);
- NSKeyedArchiver 數(shù)據(jù)序列化,只有遵守了NSCoding或 NSSecureCoding(更為安全的歸檔協(xié)議)協(xié)議,并且實(shí)現(xiàn)了協(xié)議里歸檔與解歸檔的方法的的類創(chuàng)建的對(duì)象才能夠進(jìn)行歸檔
一般是對(duì)自己創(chuàng)建的模型進(jìn)行存儲(chǔ)是會(huì)用到,
需要主要以下三點(diǎn):
1.必須遵循并實(shí)現(xiàn)NSCoding協(xié)議
2.保存文件的擴(kuò)展名可以任意指定
3.繼承時(shí)必須先調(diào)用父類的歸檔解檔方法
eg:建一個(gè)Person類
.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface Person : NSObject
@property (strong, nonatomic)UIImage *avatar;
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) NSInteger age;
@end
.m
#import "Person.h"
@implementation Person
//解檔
- (id)initWithCoder:(NSCoder *)aDecoder
{
if ([super init])
{
self.avatar = [aDecoder decodeObjectForKey:@"avatar"];
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
//歸檔
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.avatar forKey:@"avatar"];
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
}
@end
存入person:
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/person.plist"];
Person *person = [[Person alloc] init];
person.avatar = [UIImage imageNamed:@"ss"];
person.name = @"小白";
person.age = 22;
[NSKeyedArchiver archiveRootObject:person toFile:path];
讀取person:
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/person.plist"];
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if (person)
{
NSLog(@"名字是:%@",person.name);
NSLog(@"年齡是:%ld",person.age);
}
3.FMDB
大量數(shù)據(jù)的存儲(chǔ)我們一般用FMDB,比如聊天中的信息一般是用FMDB存儲(chǔ)的,F(xiàn)MDB是在sqlite的基礎(chǔ)上進(jìn)行封裝的.
一般我們?cè)谑褂脮r(shí)會(huì)對(duì)FMDB進(jìn)行再次封裝,我們對(duì)數(shù)據(jù)的操作一般也就是增,刪,改,查。將fmdb 封裝成一個(gè)工具類。將需要存儲(chǔ)的信息封裝成一個(gè)model。代碼如下:
PersonInfoModel.h
#import <Foundation/Foundation.h>
@interface PersonInfoModel : NSObject
@property (nonatomic,strong)NSString *userName;
@property (nonatomic,strong)NSString *headImage;
@property (nonatomic,strong)NSString *nickname;
@property (nonatomic,assign)NSInteger haveMoney;
@end
FmdbTool.h
#import <Foundation/Foundation.h>
@class FMDatabase;
@class PersonInfoModel;
@interface FmdbTool : NSObject
@property (nonatomic, strong) FMDatabase *db;
+ (instancetype)shareManager;
//查找
- (PersonInfoModel *)selectInfoWithUserName:(NSString *)username;
//增加
- (void)saveInfoWithPersonalModel:(PersonInfoModel *)model;
//刪除
- (void)deleteInfoWithUserName:(NSString *)username;
//改
- (void)updateInfoWithPersonModel:(PersonInfoModel *)model;
@end
FmdbTool.m
#import "FmdbTool.h"
#import "FMDB.h"
#import "PersonInfoModel.h"
static FmdbTool *__help = nil;
@implementation FmdbTool
+ (instancetype)shareManager
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__help = [[FmdbTool alloc] init];
[__help createDataBase];
[__help createTableView];
});
return __help;
}
- (void)createDataBase
{
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/conversionInfo.sqlite"];
_db = [[FMDatabase alloc] initWithPath:path];
[_db open];
[_db close];
}
-(void)createTableView
{
NSString *sql = @"CREATE TABLE IF NOT EXISTS ConversionInfo(conversionInfo_id INTEGER PRIMARY KEY AUTOINCREMENT,userName TEXT,headImage TEXT,nickname TEXT,haveMoney INTEGER)";
if ([self.db open])
{
if ([_db executeUpdate:sql])
{
NSLog(@"創(chuàng)建表成功");
}
else
{
NSLog(@"創(chuàng)建表失敗");
}
[self.db close];
}
else
{
NSLog(@"打開(kāi)數(shù)據(jù)庫(kù)失敗");
}
}
- (PersonInfoModel *)selectInfoWithUserName:(NSString *)username
{
[_db open];
NSString *sql = [NSString stringWithFormat:@"SELECT * FROM ConversionInfo where userName='%@'",username];
FMResultSet *set = [_db executeQuery:sql];
PersonInfoModel *model = [[PersonInfoModel alloc] init];
while ([set next])
{
// int studentID = [set intForColumn:@"student_id"];
NSString *name = [set stringForColumn:@"userName"];
NSString *headImage = [set stringForColumn:@"headImage"];
NSString *nickname = [set stringForColumn:@"nickname"];
int haveMoney = [set intForColumn:@"haveMoney"];
model.userName = name;
model.headImage = headImage;
model.nickname = nickname;
model.haveMoney = haveMoney;
// NSDictionary *dictionary = [set resultDictionary];
// NSLog(@"%d,%@,%d,%@",studentID,name,age,dictionary);
}
[set close];
[_db close];
return model;
}
- (void)saveInfoWithPersonalModel:(PersonInfoModel *)model
{
[_db open];
NSString *sql = [NSString stringWithFormat:@"INSERT INTO ConversionInfo(userName, headImage, nickname,haveMoney) VALUES('%@','%@','%@',%ld)",model.userName,model.headImage,model.nickname,(long)model.haveMoney];
// NSString *sql = @"insert into app (_id,updated,cover,title) values (?,?,?,?)"
BOOL result = [_db executeUpdate:sql];
// BOOL result = [_db executeUpdate:@"%@",sql];
if (result)
{
NSLog(@"插入成功");
}
else
{
NSLog(@"插入失敗");
}
[_db close];
}
- (void)deleteInfoWithUserName:(NSString *)username
{
[_db open];
NSString *sql = [NSString stringWithFormat:@"DELETE FROM ConversionInfo where userName='%@'",username];
BOOL result = [_db executeUpdate:sql];
if (result)
{
NSLog(@"刪除成功");
}
else
{
NSLog(@"刪除失敗");
}
[_db close];
}
- (void)updateInfoWithPersonModel:(PersonInfoModel *)model
{
[_db open];
NSString *sql = [NSString stringWithFormat:@"update ConversionInfo set headImage='%@',nickname='%@',haveMoney=%ld where userName='%@'",model.headImage,model.nickname,(long)model.haveMoney,model.userName];
BOOL result = [_db executeUpdate:sql];
if (result)
{
NSLog(@"修改成功");
}
else
{
NSLog(@"修改失敗");
}
[_db close];
}
@end
- CoreData 這個(gè)基本很少用
5.KeyChain
iOS keychain 是一個(gè)相對(duì)獨(dú)立的空間,保存到keychain鑰匙串中的信息不會(huì)因?yàn)樾遁d/重裝app而丟失, 。相對(duì)于NSUserDefaults、plist文件保存等一般方式,keychain保存更為安全。所以我們會(huì)用keyChain保存一些私密信息,比如密碼、證書、設(shè)備唯一碼(把獲取到用戶設(shè)備的唯一ID 存到keychain 里面這樣卸載或重裝之后還可以獲取到id,保證了一個(gè)設(shè)備一個(gè)ID)等等。keychain是用SQLite進(jìn)行存儲(chǔ)的。用蘋果的話來(lái)說(shuō)是一個(gè)專業(yè)的數(shù)據(jù)庫(kù),加密我們保存的數(shù)據(jù),可以通過(guò)metadata(attributes)進(jìn)行高效的搜索。keychain適合保存一些比較小的數(shù)據(jù)量的數(shù)據(jù),如果要保存大的數(shù)據(jù),可以考慮文件的形式存儲(chǔ)在磁盤上,在keychain里面保存解密這個(gè)文件的密鑰。
如果需要在應(yīng)用里使用使用keyChain,我們需要導(dǎo)入Security.framework ,keychain的操作接口聲明在頭文件SecItem.h里。但是自己需要自己封裝有點(diǎn)復(fù)雜。我這邊使用的是第三方封裝好的SSKeychain。導(dǎo)入即可使用
保存密碼:
if ( [SAMKeychain setPassword:@"白首如新,傾蓋如故" forService:@"test" account:@"testAccount"])
{
NSLog(@"keyChin存儲(chǔ)成功");
}
else
{
NSLog(@"keyChain存儲(chǔ)失敗");
}
讀取密碼
NSString *passWord = [SAMKeychain passwordForService:@"test" account:@"testAccount"];
NSLog(@"passWord is %@",passWord);