【code_hyy_基礎(chǔ)】iOS持久化方式

首先這里的持久化指的是數(shù)據(jù)持久化,目前客戶端的持久化也只有這一個含義。

為何要持久化:

iOS開發(fā)可以沒有持久化,持久化更多的是業(yè)務(wù)需求;比如記錄用戶是否登陸,下次進應(yīng)用不需要再登陸。

因為iOS的沙盒機制,所以持久化分為兩類:沙盒內(nèi)和沙盒外。
一. 沙盒內(nèi)
1.NSKeyedArchiver

只要遵循了NSCoding協(xié)議并正確實現(xiàn)了initWithCoder和encodeWithCoder方法的類都可以通過NSKeyedArchiver來序列化。
歸檔使用archiveRootObject,解歸檔使用unarchiveObjectWithFile;需要指定文件路徑。

例子:
歸檔在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]方法。
2.NSUserDefaults

[NSUserDefaults standardUserDefaults]獲取NSUserDefaults對象,以key-value方式進行持久化操作。

很多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);
}

復(fù)制代碼注意事項:

  • 如果沒有調(diào)用synchronize方法,系統(tǒng)會根據(jù)I/O情況不定時刻地保存到文件中。所以如果需要立即寫入,就必須調(diào)用synchronize方法。
    偏好設(shè)置會將所有數(shù)據(jù)保存到同一個文件夾,使用同一個key,會把之前存儲的數(shù)據(jù)覆蓋。
3.plist

寫入使用writeToFile,讀取使用xxxWithContentsOfFile;需要指定文件路徑。

plist文件是通過XML文件的方式保存在目錄中 以下類型可以被序列化:

NSString;//字符串
NSMutableString;//可變字符串
NSArray;//數(shù)組
NSMutableArray;//可變數(shù)組
NSDictionary;//字典
NSMutableDictionary;//可變字典
NSData;//二進制數(shù)據(jù)
NSMutableData;//可變二進制數(shù)據(jù)
NSNumber;//基本數(shù)據(jù)
NSDate;//日期
復(fù)制代碼

這里我們就用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

<figure>
圖1.png

</figure>

4.數(shù)據(jù)庫

數(shù)據(jù)庫無疑是大量數(shù)據(jù)最好的持久化方案,數(shù)據(jù)庫目前有:sqlite、CoreData和Realm等。這里就不用回答FMDB它只是封裝了sqlite而已。

5.文件

這里要和plist區(qū)分一下,plist方式是字典/數(shù)組數(shù)據(jù)格式寫入文件;而這里的文件方式不限數(shù)據(jù)格式。

二.沙盒外
KeyChain

沙盒內(nèi)的方式在應(yīng)用被刪除后數(shù)據(jù)都會丟失,如果想要不丟失則需要使用KeyChain。
KeyChain本質(zhì)是一個sqlite數(shù)據(jù)庫,其保存的所有數(shù)據(jù)都是加密過的。
KeyChain分為私有和公有,公有則需要指定group,一個group中的應(yīng)用可以共享此KeyChain。
使用KeyChain過程中要理解下面幾個問題:

1:自己使用的KeyChain和系統(tǒng)自帶的KeyChain數(shù)據(jù)是隔離的,內(nèi)部應(yīng)該是不同數(shù)據(jù)庫文件;
2:KeyChain數(shù)據(jù)可備份到iCloud中;
3:不需要聯(lián)網(wǎng),也不用登陸iCloud賬號;一個設(shè)備一個sqlite數(shù)據(jù)庫,但是不同應(yīng)用組不共享數(shù)據(jù);
4:要在另一臺設(shè)備上使用當(dāng)前設(shè)備存儲的KeyChain信息,需要當(dāng)前設(shè)備進行數(shù)據(jù)備份,
再在另一設(shè)備上復(fù)原數(shù)據(jù);比較常用的是iCloud備份方式;
5:系統(tǒng)自帶的KeyChain中賬號密碼分類數(shù)據(jù)可在系統(tǒng)設(shè)置->賬號與密碼里面看到,
你退出iCloud賬號還是存在,只是iCloud會幫你備份如果你設(shè)置了的話;這個和照片是一樣的道理。

通常情況下,我們使用NSUserDefaults存儲數(shù)據(jù)信息,但是對于一些私密信息,但是對于一下比較私密的信息,如帳號、密碼等等,我們就需要使用更為安全的keychain了。keychain保存的信息是保存在沙盒之外的,不會因App的刪除而丟失,在用戶重新安裝了App后依然存在。其實可以把keychain理解成一個Dictionary,所有數(shù)據(jù)都以key-value的形式存儲,可以對這個Dictionary進行add、update、get、delete這四個操作。對一個應(yīng)用來說,keychain都有兩個訪問區(qū),私有和公共。

  1. Target - Capabilities - Keychain Sharing - ON

    <figure>
    圖2.png

    <figcaption></figcaption>

    </figure>

    左側(cè)的目錄會自動生成Entitlements文件,不需要自己創(chuàng)建了。

  2. 引入Security.framework

  3. 自定義一個類,取名keychain,如下:

.h文件

#import <Foundation/Foundation.h>
@interface keychain : NSObject
/**添加*/
+(void)savePassWord:(NSString *)password;
/**讀取*/
+(id)loadPassWord;
/**刪除*/
+(void)deletePassword;
@end
復(fù)制代碼

.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

持久化
iOS 數(shù)據(jù)持久化的幾種方法
聊聊iOS KeyChain

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地數(shù)據(jù)庫組件 SD...
    陽明AI閱讀 16,201評論 3 119
  • 今天參加了參加了關(guān)于價值觀的“人生大拍賣”游戲,很有意思,我們手里每個人只有5000元,有16件“商品”,25個人...
    函數(shù)閱讀 337評論 0 2
  • 郭相麟 生活能磨練一個人的意志,窮人的孩子早當(dāng)家,家庭的重?fù)?dān)壓在年幼的孩子上,幸福是看到家人一切安好! 走在崎...
    郭相麟閱讀 226評論 0 0
  • 近期有網(wǎng)友私信問養(yǎng)牛前景怎么樣?今天就該問題作出以下回答,望從事養(yǎng)牛業(yè)的朋友參考。 近期牛價不斷上漲讓眾多養(yǎng)牛戶感...
    山東牛人王哥閱讀 1,647評論 0 0

友情鏈接更多精彩內(nèi)容