简介

持久化就是将数据保存到硬盘中,让APP重启后可以使用之前保存的数据.在iOS开发中,可能会用到一下几种

  1. plist文件:属性列表
  2. preference:偏好设置
  3. NSKeyedArchiver:归档
  4. keychain:钥匙串

沙盒

在介绍存储方法之前,先说下沙盒机制.iOS程序默认情况下只能访问程序的目录,这个目录就是沙盒。 沙盒的目录结构如下:

  • 应用程序包:存放的是应用程序的源文件:资源文件和可执行文件
NSString *path = [[NSBundle mainBundle] bundlePath];
复制代码
  • Documents:比较常用的目录,itune同步会同步这个文件夹的内容,适合存储重要的数据
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
复制代码
  • Library: 1.Caches:tunes不会同步此文件夹,设个存储体积大,不需要备份的重要数据
NSString *path  = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
复制代码
  • tmpitunes不会同步,系统可能在应用没运行时就删除该目录的文件,适合存临时文件,用完就删除:
NSString *path  = NSTemporaryDirectory();
复制代码

一、plist文件(序列化)

plist文件是通过XML文件的方式保存在目录中 以下类型可以被序列化:

NSString;//字符串
NSMutableString;//可变字符串
NSArray;//数组
NSMutableArray;//可变数组
NSDictionary;//字典
NSMutableDictionary;//可变字典
NSData;//二进制数据
NSMutableData;//可变二进制数据
NSNumber;//基本数据
NSDate;//日期
复制代码

这里我们就用NSDictionary当例子,其他的类型和这个方法类似;

/**
 写入数据
 */
-(void)writeToPlist:(NSDictionary *)dict plistName:(NSString *)plistName{
    //存取路径
    NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    //路径中文件名
    NSString *filePath = [path stringByAppendingPathComponent:plistName];
    //序列化,把数据存入指定目录的plist文件
    [dict writeToFile:filePath atomically:YES];
}
/**
 根据plist文件名读取数据
 */
-(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:偏好设置

很多iOS应用都支持偏好设置,比如保存用户名、密码、字体等设置。每个应用都有NSUserDefaults实例,通过它来读取偏好设置。一般不要在偏好设置中保存其他数据。
偏好设置是key-value的方式存取和读取的。 使用方法:

/**
 保存数据
 */
- (IBAction)saveDate:(UIButton *)sender {
    //获得NSUserDefaults文件
    NSUserDefaults *userDefaultes = [NSUserDefaults standardUserDefaults];
    //向偏好设置中写入内容
    [userDefaultes setObject:@"lcf" forKey:@"name"];
    [userDefaultes setInteger:26 forKey:@"age"];
    [userDefaultes setObject:@"boy" forKey:@"sex"];
    //立即同步设置
    [userDefaultes synchronize];
}
/**
 读取数据
 */
- (IBAction)readDate:(UIButton *)sender {
    //获得NSUserDefaults文件
    NSUserDefaults *userDefaultes = [NSUserDefaults standardUserDefaults];
    //读取偏好设置
    NSString *name = [userDefaultes objectForKey:@"name"];
    NSInteger age = [userDefaultes integerForKey:@"age"];
    NSString *sexStr = [userDefaultes objectForKey:@"sex"];
    NSLog(@"\n%@\n%ld\n%@",name,age,sexStr);
}
复制代码

注意事项:

  • 如果没有调用synchronize方法,系统会根据I/O情况不定时刻地保存到文件中。所以如果需要立即写入,就必须调用synchronize方法。
  • 偏好设置会将所有数据保存到同一个文件夹,使用同一个key,会把之前存储的数据覆盖。

三、NSKeyedArchiver:归档和解档

归档在iOS中是另一种形式的序列化,只要遵循了NSCoding协议的对象都可以通过它来实现序列化。由于大多是类都遵循了NSCoding协议,因此,对于大多数类来说,归档是比较容易实现的。

  1. 遵循NSCoding协议,其中有2个方法是必须实现的:
  1. initWithCoder
  2. encodeWithCoder
//遵循NSCoding协议
@interface DWSave : NSObject<NSCoding>
/**name*/
@property(nonatomic ,copy)NSString *name;
/**age*/
@property(nonatomic ,assign)NSInteger age;
/**sex*/
@property(nonatomic ,assign)BOOL sex;
@end
//以上内容要写在.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];
    //设置数据
    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协议 保存文件的扩展名可以自定义 如果需要归档的类是某个自定义类的子类时,就需要在归档和解档之前实现父类的归档和解档方法。[super encodeWithCoder:aCoder][super initWithCoder:aDecoder]方法。

keychain:钥匙串

通常情况下,我们使用NSUserDefaults存储数据信息,但是对于一些私密信息,但是对于一下比较私密的信息,如帐号、密码等等,我们就需要使用更为安全的keychain了。keychain保存的信息是保存在沙盒之外的,不会因App的删除而丢失,在用户重新安装了App后依然存在。其实可以把keychain理解成一个Dictionary,所有数据都以key-value的形式存储,可以对这个Dictionary进行add、update、get、delete这四个操作。对一个应用来说,keychain都有两个访问区,私有和公共。

  1. Target - Capabilities - Keychain Sharing - ON
  2. 引入Security.framework
  3. 自定义一个类,取名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部分代码摘自网络

结束语: 文章中可能会存在错误,欢迎 指正。 Demo