美文网首页
【code_hyy_基础】iOS持久化方式

【code_hyy_基础】iOS持久化方式

作者: Hyyqinfen | 来源:发表于2018-11-06 08:43 被阅读0次

    首先这里的持久化指的是数据持久化,目前客户端的持久化也只有这一个含义。

    为何要持久化:

    iOS开发可以没有持久化,持久化更多的是业务需求;比如记录用户是否登陆,下次进应用不需要再登陆。

    因为iOS的沙盒机制,所以持久化分为两类:沙盒内和沙盒外。
    一. 沙盒内
    1.NSKeyedArchiver

    只要遵循了NSCoding协议并正确实现了initWithCoder和encodeWithCoder方法的类都可以通过NSKeyedArchiver来序列化。
    归档使用archiveRootObject,解归档使用unarchiveObjectWithFile;需要指定文件路径。

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

    遵循NSCoding协议,其中有2个方法是必须实现的:
    initWithCoder
    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]方法。
    2.NSUserDefaults

    [NSUserDefaults standardUserDefaults]获取NSUserDefaults对象,以key-value方式进行持久化操作。

    很多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,会把之前存储的数据覆盖。
    3.plist

    写入使用writeToFile,读取使用xxxWithContentsOfFile;需要指定文件路径。

    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

    <figure> 图1.png

    </figure>

    4.数据库

    数据库无疑是大量数据最好的持久化方案,数据库目前有:sqlite、CoreData和Realm等。这里就不用回答FMDB它只是封装了sqlite而已。

    5.文件

    这里要和plist区分一下,plist方式是字典/数组数据格式写入文件;而这里的文件方式不限数据格式。

    二.沙盒外
    KeyChain

    沙盒内的方式在应用被删除后数据都会丢失,如果想要不丢失则需要使用KeyChain。
    KeyChain本质是一个sqlite数据库,其保存的所有数据都是加密过的。
    KeyChain分为私有和公有,公有则需要指定group,一个group中的应用可以共享此KeyChain。
    使用KeyChain过程中要理解下面几个问题:

    1:自己使用的KeyChain和系统自带的KeyChain数据是隔离的,内部应该是不同数据库文件;
    2:KeyChain数据可备份到iCloud中;
    3:不需要联网,也不用登陆iCloud账号;一个设备一个sqlite数据库,但是不同应用组不共享数据;
    4:要在另一台设备上使用当前设备存储的KeyChain信息,需要当前设备进行数据备份,
    再在另一设备上复原数据;比较常用的是iCloud备份方式;
    5:系统自带的KeyChain中账号密码分类数据可在系统设置->账号与密码里面看到,
    你退出iCloud账号还是存在,只是iCloud会帮你备份如果你设置了的话;这个和照片是一样的道理。

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

    1. Target - Capabilities - Keychain Sharing - ON

      <figure> 图2.png

      <figcaption></figcaption>

      </figure>

      左侧的目录会自动生成Entitlements文件,不需要自己创建了。

    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
    

    持久化
    iOS 数据持久化的几种方法
    聊聊iOS KeyChain

    相关文章

      网友评论

          本文标题:【code_hyy_基础】iOS持久化方式

          本文链接:https://www.haomeiwen.com/subject/wuqstqtx.html