簡介
持久化就是將數(shù)據(jù)保存到硬盤中,讓APP重啟后可以使用之前保存的數(shù)據(jù).在iOS開發(fā)中,可能會用到一下幾種
- plist文件:屬性列表
- preference:偏好設(shè)置
- NSKeyedArchiver:歸檔
- keychain:鑰匙串
沙盒
在介紹存儲方法之前,先說下沙盒機制.iOS程序默認(rèn)情況下只能訪問程序的目錄,這個目錄就是沙盒。
沙盒的目錄結(jié)構(gòu)如下:
-
應(yīng)用程序包:
存放的是應(yīng)用程序的源文件:資源文件和可執(zhí)行文件
NSString *path = [[NSBundle mainBundle] bundlePath];
-
Documents:
比較常用的目錄,itune同步會同步這個文件夾的內(nèi)容需曾,適合存儲重要的數(shù)據(jù)
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
-
Library:
1.Caches:tunes不會同步此文件夾竿滨,設(shè)個存儲體積大,不需要備份的重要數(shù)據(jù)
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
-
tmp
itunes不會同步迹鹅,系統(tǒng)可能在應(yīng)用沒運行時就刪除該目錄的文件,適合存臨時文件,用完就刪除:
NSString *path = NSTemporaryDirectory();
一巫财、plist文件(序列化)
plist文件是通過XML文件的方式保存在目錄中
以下類型可以被序列化:
NSString;//字符串
NSMutableString;//可變字符串
NSArray;//數(shù)組
NSMutableArray;//可變數(shù)組
NSDictionary;//字典
NSMutableDictionary;//可變字典
NSData;//二進制數(shù)據(jù)
NSMutableData;//可變二進制數(shù)據(jù)
NSNumber;//基本數(shù)據(jù)
NSDate;//日期
這里我們就用NSDictionary當(dāng)例子,其他的類型和這個方法類似;
/**
寫入數(shù)據(jù)
*/
-(void)writeToPlist:(NSDictionary *)dict plistName:(NSString *)plistName{
//存取路徑
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
//路徑中文件名
NSString *filePath = [path stringByAppendingPathComponent:plistName];
//序列化,把數(shù)據(jù)存入指定目錄的plist文件
[dict writeToFile:filePath atomically:YES];
}
/**
根據(jù)plist文件名讀取數(shù)據(jù)
*/
-(NSDictionary *)readFromPlistWithPlistName:(NSString *)plistName{
//存取路徑
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
//路徑中文件名
NSString *filePath = [path stringByAppendingPathComponent:plistName];
NSDictionary *resultDict = [NSDictionary dictionaryWithContentsOfFile:filePath];
return resultDict;
}
atomically是否先寫入輔助文件,增加安全性的寫入文件方法,一般都是YES
二、preference:偏好設(shè)置
很多iOS應(yīng)用都支持偏好設(shè)置哩陕,比如保存用戶名平项、密碼、字體等設(shè)置悍及。每個應(yīng)用都有NSUserDefaults實例闽瓢,通過它來讀取偏好設(shè)置。一般不要在偏好設(shè)置中保存其他數(shù)據(jù)心赶。
偏好設(shè)置是key-value
的方式存取和讀取的扣讼。
使用方法:
/**
保存數(shù)據(jù)
*/
- (IBAction)saveDate:(UIButton *)sender {
//獲得NSUserDefaults文件
NSUserDefaults *userDefaultes = [NSUserDefaults standardUserDefaults];
//向偏好設(shè)置中寫入內(nèi)容
[userDefaultes setObject:@"lcf" forKey:@"name"];
[userDefaultes setInteger:26 forKey:@"age"];
[userDefaultes setObject:@"boy" forKey:@"sex"];
//立即同步設(shè)置
[userDefaultes synchronize];
}
/**
讀取數(shù)據(jù)
*/
- (IBAction)readDate:(UIButton *)sender {
//獲得NSUserDefaults文件
NSUserDefaults *userDefaultes = [NSUserDefaults standardUserDefaults];
//讀取偏好設(shè)置
NSString *name = [userDefaultes objectForKey:@"name"];
NSInteger age = [userDefaultes integerForKey:@"age"];
NSString *sexStr = [userDefaultes objectForKey:@"sex"];
NSLog(@"\n%@\n%ld\n%@",name,age,sexStr);
}
注意事項:
- 如果沒有調(diào)用
synchronize
方法,系統(tǒng)會根據(jù)I/O情況不定時刻地保存到文件中缨叫。所以如果需要立即寫入椭符,就必須調(diào)用synchronize
方法。 - 偏好設(shè)置會將所有數(shù)據(jù)保存到同一個文件夾弯汰,使用同一個
key
艰山,會把之前存儲的數(shù)據(jù)覆蓋。
三咏闪、NSKeyedArchiver:歸檔和解檔
歸檔在iOS中是另一種形式的序列化曙搬,只要遵循了NSCoding協(xié)議
的對象都可以通過它來實現(xiàn)序列化。由于大多是類都遵循了NSCoding協(xié)議
鸽嫂,因此纵装,對于大多數(shù)類來說,歸檔是比較容易實現(xiàn)的据某。
- 遵循
NSCoding協(xié)議
,其中有2個方法是必須實現(xiàn)的:initWithCoder
encodeWithCoder
//遵循NSCoding協(xié)議
@interface DWSave : NSObject<NSCoding>
/**name*/
@property(nonatomic ,copy)NSString *name;
/**age*/
@property(nonatomic ,assign)NSInteger age;
/**sex*/
@property(nonatomic ,assign)BOOL sex;
@end
//以上內(nèi)容要寫在.h文件中
@implementation DWSave
//歸檔
-(void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
[aCoder encodeBool:self.sex forKey:@"sex"];
}
//解檔
-(instancetype)initWithCoder:(NSCoder *)aDecoder;{
if (self = [super init]) {
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
self.sex = [aDecoder decodeBoolForKey:@"sex"];
}
return self;
}
@end
NSKeyedArchiver歸檔
//保存地址
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
//文件名
NSString *filePath = [path stringByAppendingPathComponent:@"wyp.data"];
DWSave *save = [[DWSave alloc] init];
//設(shè)置數(shù)據(jù)
save.name = @"lcf";
save.age = 26;
save.sex = 1;
[NSKeyedArchiver archiveRootObject:save toFile:filePath];
NSKeyedUnarchiver解檔
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [path stringByAppendingPathComponent:@"wyp.data"];
DWSave *save = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
if (save) {
NSLog(@"\n%@\n%ld\n%d",save.name,save.age,save.sex);
}
必須要遵循NSCoding協(xié)議
保存文件的擴展名可以自定義
如果需要歸檔的類是某個自定義類的子類時橡娄,就需要在歸檔和解檔之前實現(xiàn)父類的歸檔和解檔方法。[super encodeWithCoder:aCoder]
和[super initWithCoder:aDecoder]
方法癣籽。
keychain:鑰匙串
通常情況下挽唉,我們使用NSUserDefaults存儲數(shù)據(jù)信息滤祖,但是對于一些私密信息,但是對于一下比較私密的信息瓶籽,如帳號匠童、密碼等等,我們就需要使用更為安全的keychain了塑顺。keychain保存的信息是保存在沙盒之外的汤求,不會因App的刪除而丟失,在用戶重新安裝了App后依然存在严拒。其實可以把keychain理解成一個Dictionary扬绪,所有數(shù)據(jù)都以key-value
的形式存儲,可以對這個Dictionary進行add裤唠、update挤牛、get、delete這四個操作巧骚。對一個應(yīng)用來說赊颠,keychain都有兩個訪問區(qū),私有和公共劈彪。
-
Target - Capabilities - Keychain Sharing - ON
左側(cè)的目錄會自動生成Entitlements文件竣蹦,不需要自己創(chuàng)建了。
- 引入Security.framework
- 自定義一個類沧奴,取名keychain痘括,如下:
.h文件
#import <Foundation/Foundation.h>
@interface keychain : NSObject
/**添加*/
+(void)savePassWord:(NSString *)password;
/**讀取*/
+(id)loadPassWord;
/**刪除*/
+(void)deletePassword;
@end
.m文件
#import "keychain.h"
#import <Security/Security.h>
@implementation keychain
static NSString *const KEY_KEYCHAIN = @"LCF";
static NSString *const KEY_PASSWORD = @"PASSWORD";
/**添加*/
+(void)savePassWord:(NSString *)password{
NSMutableDictionary *infoDict = [NSMutableDictionary dictionary];
[infoDict setObject:password forKey:KEY_PASSWORD];
[keychain save:KEY_KEYCHAIN data:infoDict];
}
/**讀取*/
+(id)loadPassWord{
NSMutableDictionary *infoDict = [keychain load:KEY_KEYCHAIN];
return [infoDict objectForKey:KEY_PASSWORD];
}
/**刪除*/
+(void)deletePassword{
[self delete:KEY_KEYCHAIN];
}
+(NSMutableDictionary *)getKeychainQuery:(NSString *)service {
return [NSMutableDictionary dictionaryWithObjectsAndKeys:
(id)kSecClassGenericPassword,(id)kSecClass,
service, (id)kSecAttrService,
service, (id)kSecAttrAccount,
(id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,
nil];
}
+(void)save:(NSString *)service data:(id)data {
//Get search dictionary
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
//Delete old item before add new item
SecItemDelete((CFDictionaryRef)keychainQuery);
//Add new object to search dictionary(Attention:the data format)
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
//Add item to keychain with the search dictionary
SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
}
+(id)load:(NSString *)service {
id ret = nil;
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
//Configure the search setting
//Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
[keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
CFDataRef keyData = NULL;
if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
@try {
ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
} @catch (NSException *e) {
NSLog(@"Unarchive of %@ failed: %@", service, e);
} @finally {
}
}
if (keyData)
CFRelease(keyData);
return ret;
}
+ (void)delete:(NSString *)service {
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
SecItemDelete((CFDictionaryRef)keychainQuery);
}
@end
kechain部分代碼摘自網(wǎng)絡(luò)
結(jié)束語:
文章中可能會存在錯誤,歡迎 指正滔吠。
Demo