iOS-数据存储

一.沙盒

每个iOS应用都有⾃己的应⽤沙盒(应用沙盒就是文件系统目录), 与其他文件系统隔离。应⽤必须待在⾃己的沙盒里,其他应用不能访问该沙盒(在iOS8中已经开放访问)

应⽤沙盒的文件系统⽬录,如下图所示
iOS-数据存储_第1张图片
沙盒.png

可以看出, 沙盒里面有四个文件夹, 分别是Documents, Lirary, SystemData, tmp
Lirary里面又有Caches, Preferences两个文件夹

1. Documents

默认是备份的(使用itunes或者iCloud会备份的时候)
大文件尽量不要存储在这个目录下(视频文件)如果不做任何处理,审核,检查出来,就会被拒
如果不想被拒, 下面的操作二选其一
① 文件非备份操作设置
因为Documents文件夹默认是备份的, 我们需要写以下代码

// 设置非备份
//#import 
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL*)URL
{
    const char* filePath = [[URL path] fileSystemRepresentation];
    const char* attrName = "com.apple.MobileBackup";
    u_int8_t attrValue = 1;
    
    int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
    return result == 0;
}

这样documents文件夹下的东西默认就不会备份了

② 大文件放到其他目录

2. Lirary

Lirary文件下有两个文件夹Caches和Preferences, 都是默认备份的

Caches 缓存(比如系统截屏,网络缓存等 )
Preferences(plist文件 NSUserDefaults)

① Caches
当我们使用截屏, 或者网络请求的时候, 会在Caches产生缓存.

网络请求示例代码:

//网络缓存
- (void)netLoadTask{
    NSString *urlStr = [NSString stringWithFormat:@"http://svr.tuliu.com/center/front/app/util/updateVersions?versions_id=1&system_type=1"];
    
    NSURLSession *session = [NSURLSession sharedSession];
    // 默认的缓存存在disk, 这时候Caches里面有缓存
    // [NSURLSessionConfiguration defaultSessionConfiguration]; 
    // 缓存存在内存, 这时候在Caches里面是没有缓存的
    // NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; 
    NSURLSessionTask *task =  [session dataTaskWithURL:[NSURL URLWithString:urlStr] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSDictionary *infoDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
        NSLog(@"%@", infoDict);
    }];
    [task resume];
}

运行代码, 验证如下:
Caches.png

其中, 网络请求的缓存是一些非加密的数据库文件

使用:比如我们的app有一个清除缓存的功能,这时候我们首先要清除Caches文件夹的内容, 其次再清除我们自己创建的一些文件夹

② Preferences
我们使用NSUserDefaults存储的数据在这里

3. SystemData
4. tmp

tmp (临时文件夹)
当内存不足的时候系统有可能会把tmp清空, 所以一些重要的文件不会要放这里

5. Bundle

Bundle是资源包的意思, 就是个文件夹, 一些HUD或者MJRefresh都带有Bundle, 不参与编译, 里面别放代码

如下图, 项目中又添加了一个Bundle
iOS-数据存储_第2张图片
Bundle.png

如果想要获取主Bundle的图片直接

_imageView.image = [UIImage imageNamed:@"2.png"];

但是如果想要获取新添加的Bundle的图片, 就要先拿到新添加的Bundle, 如下

  // 用代码取路径
  NSString *boudlePath = [[NSBundle mainBundle] pathForResource:@"EOCBundle" ofType:@"bundle"];
  //获取新的Bundle
  NSBundle *eocBoundle = [NSBundle bundleWithPath:boudlePath];
    
  NSString *imagePath = [eocBoundle pathForResource:@"11" ofType:@"png"];
  _imageView.image = [UIImage imageWithContentsOfFile:imagePath];

获取主Bundle资源文件路径的方法:

//例如获取程序包中一个图片资源(apple.png)路径的方法:
NSString *imagePath = [[NSBundle mainBundle] pathForResource:@”apple” ofType:@”png”];
UIImage *appleImage = [[UIImage alloc] initWithContentsOfFile:imagePath];
//代码中的mainBundle类方法用于返回一个代表应用程序包的对象。

补充:项目中黄色文件夹和蓝色文件夹的区别
我们项目中一般创建的都是黄色文件夹, 黄色的是参与编译的

黄色.png

项目中的蓝色文件夹是资源文件夹, 不参与编译, 比如
蓝色.png

获取APP沙盒路径的两种方式

  1. 使用NSSearchPathForDirectoriesInDomains方法
//Domain领域  Mask面具,掩护,掩饰
//1. 获取Documents目录路径的方法:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);
NSString *docDir = [paths objectAtIndex:0];
//2. 获取Caches目录路径的方法:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask,YES);
NSString *cachesDir = [paths objectAtIndex:0];
/*
关于参数:
NSUserDomainMask 在用户目录下查找
YES 代表用户目录的~符号在iOS中识别~
NSDocumentDirectory 查找Documents文件夹
*/
  1. 使用沙盒根目录拼接

利用沙盒根目录拼接”Documents”字符串

不建议采用,因为新版本的操作系统可能会修改目录名
NSString *home = NSHomeDirectory();
NSString *documents = [home stringByAppendingPathComponent:@"Documents"]; 

另外可以直接使用NSTemporaryDirectory()直接获取tmp的路径

二. NSUserDefaults

很多iOS应用都支持偏好设置,比如保存用户名、密码、字体大小等设置,iOS提供了一套标准的解决方案来为应用加入偏好设置功能
每个应用都有个NSUserDefaults实例,通过它来存取偏好设置。比如,保存用户名、字体大小、是否自动登录

它是XML属性列表, 属性列表是一种XML格式的文件,拓展名为plist

存储位置:Library -> Preferences
位置.png

存储格式:
iOS-数据存储_第3张图片
格式.png

简单使用:

//1.获取NSUserDefaults对象
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults]; 
//2保存数据
[defaults setObject:@"yangyong" forKey:@"name"];
//3.强制让数据立刻保存
[defaults synchronize];

//4.读取数据
NSString *name=[defaults objectForKey:@"name"];

注意:

  1. 使用偏好设置对数据进行保存之后, 它保存到系统的时间是不确定的,会在将来某一时间点自动将数据保存到Preferences文件夹下面,如果需要即刻将数据存储,可以使用[defaults synchronize]

  2. 所有的信息都写在一个plist文件中. 根据plist文件可以保存的数据类型我们也可以推测出NSUserDefaults可以保存的数据类型, 所以有些类型我们要转成NSData再保存在NSUserDefaults中(不推荐)

关于沙盒可参考:沙盒和NSBundle

三. NSKeyedUnarchiver(归档)

在使用plist进行数据存储和读取,只适用于系统自带的一些常用类型才能用,且必须先获取路径相对麻烦
对于NSUserDefaults, 主要用于存储应用的设置信息, 而且它的本质还是plist文件, plist都能直接打开, 相对不安全
这两者都有一个致命的缺陷,只能存储常用的类型(plist文件支持的类型)。所以我们要介绍归档
归档可以实现把自定义的对象存放在文件中

使用方法:
先自定义一个对象, 对象遵守NSCoding协议,并且实现两个协议方法, 如果不实现两个协议方法, 会报错

@interface Person : NSObject
//姓名
@property(nonatomic,copy)NSString *name;
//年龄
@property(nonatomic,assign)int age;
//身高
@property(nonatomic,assign)double height;
@end
// 当将一个自定义对象保存到文件的时候就会调用该方法
// 在该方法中说清楚存储自定义对象的哪些属性
-(void)encodeWithCoder:(NSCoder *)aCoder
{
     NSLog(@"调用了encodeWithCoder:方法");
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeInteger:self.age forKey:@"age"];
    [aCoder encodeDouble:self.height forKey:@"height"];
}

// 当从文件中读取一个对象的时候就会调用该方法
// 在该方法中说清楚怎么读取文件中的对象
-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
    NSLog(@"调用了initWithCoder:方法");
    //注意:在构造方法中需要先初始化父类的方法
    if (self=[super init]) {
        self.name=[aDecoder decodeObjectForKey:@"name"];
        self.age=[aDecoder decodeIntegerForKey:@"age"];
        self.height=[aDecoder decodeDoubleForKey:@"height"];
    }
    return self;
}

控制器代码如下:

    //创建对象
    Person *p = [[Person alloc] init];
    p.name = @"徐金城";
    p.age = 15;
    p.height = 180;
    
    //获取路径
    //NSSearchPathForDirectoriesInDomains返回的是一个数组, 内容只有一个
    NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
    NSString *path=[docPath stringByAppendingPathComponent:@"xujincheng"];
    NSLog(@"path=%@",path);
    
    //将自定义的对象保存到文件中
    //[NSKeyedArchiver archivedDataWithRootObject:<#(nonnull id)#> requiringSecureCoding:<#(BOOL)#> error:<#(NSError * _Nullable __autoreleasing * _Nullable)#>];
    BOOL flag = [NSKeyedArchiver archiveRootObject:p toFile:path];//YES归档成功,NO归档失败
    
    //读取对象
    Person *p2 = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    NSLog(@"%@,%d,%.1f",p2.name,p2.age,p2.height);

运行后,归档的文件如下:
iOS-数据存储_第4张图片
归档.png

打印结果如下:

path=/Users/xujincheng/Library/Developer/CoreSimulator/Devices/A3BD602E-4365-4B04-9FA4-61186DCE8ADA/data/Containers/Data/Application/B4343BA9-74F9-44D9-923D-5318F2086CDA/Documents/xujincheng
2019-11-05 10:20:00.535875+0800 归档[82834:6785993] 调用了encodeWithCoder:方法
2019-11-05 10:20:00.536683+0800 归档[82834:6785993] 调用了initWithCoder:方法
2019-11-05 10:20:00.536810+0800 归档[82834:6785993] 徐金城,15,180.0

注意:

  1. 遵守NSCoding协议,并实现该协议中的两个方法。
  2. 如果是继承,则子类一定要重写那两个方法。因为person的子类在存取的时候,会去子类中去找调用的方法,没找到那么它就去父类中找,所以最后保存和读取的时候新增加的属性会被忽略。需要先调用父类的方法,先初始化父类的,再初始化子类的。
  3. NSCoding可以存储任意对象到沙盒中,只要遵守他的Coding协议即可. 要将一个自定义的类进行归档,那么类里面的每个属性都必须是可以被归档的,如果是不能归档的类型,我们可以把他转化为NSValue进行归档,然后在读出来的时候在转化为相应的类。
  4. 保存数据的文件的后缀名可以随意命名。
  5. 通过plist保存的数据是直接显示的,不安全。通过归档方法保存的数据在文件中打开是乱码的,更安全。

上面的方法我们可以实现把一个对象归档起来, 但是如果我们想把多个对象归档起来应该怎么操作呢?

如何归档多个对象

上面我们使用的是NSKeyedArchiver的类方法. 只能将一个对象归档起来. 如果想要归档多个对象, 需要用NSKeyedArchiver的对象方法, 将各个对象归档到archiver对象对应的data数据中, 再把data数据写入沙盒.

代码如下:

    //归档
    NSArray *array = [NSArray arrayWithObjects:@"zhangsan",@"lisi",nil];
    NSMutableData *data = [NSMutableData data];
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    
    //编码
    [archiver encodeObject:array forKey:@"array"];
    [archiver encodeInt:100 forKey:@"scope"];
    [archiver encodeObject:@"jack" forKey:@"name"];
    
    //结束归档
    [archiver finishEncoding];
    
    //路径
    NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent:@"array.src"];
    
    //将Data写入
    NSLog(@"path=%@",filePath);
    BOOL success = [data writeToFile:filePath atomically:YES];
    if(success){
        NSLog(@"归档成功");
    }
    
    //解档
    //从路径中initWithContentsOfFile出来data
    NSMutableData *data2 = [[NSMutableData alloc] initWithContentsOfFile:filePath];
    
    //创建解归档对象,对data中的数据进行解归档
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data2];
    
    //解档
    NSArray *array2 = [unarchiver decodeObjectForKey:@"array"];
    NSLog(@"%@",array2);
    NSString* name = [unarchiver decodeObjectForKey:@"name"];
    NSLog(@"%@",name);
    
    //结束解档
    [unarchiver finishDecoding];

打印结果如下:

2019-11-05 11:08:08.056992+0800 归档[83805:6842597] path=/Users/xujincheng/Library/Developer/CoreSimulator/Devices/A3BD602E-4365-4B04-9FA4-61186DCE8ADA/data/Containers/Data/Application/60A736B2-91A1-4B28-B692-574834D77AB6/array.src
2019-11-05 11:08:08.057880+0800 归档[83805:6842597] 归档成功
2019-11-05 11:08:08.058142+0800 归档[83805:6842597] (
                                                       zhangsan,
                                                       lisi
                                                       )
2019-11-05 11:08:08.058214+0800 归档[83805:6842597] jack

四. writeToFile

如果对象是NSString、NSDictionary、NSArray、NSData、 NSNumber等类型,就可以使用writeToFile:atomically:⽅法, 直接将对象写入指定路径下
如果是音频文件,文本文件,视频文件就必须通过路径获取通过NSData的dataWithContentsOfFile接收, 再将NSData写入指定路径

比如:字符串的写入和读取

    //将字符串写入到桌面并读取
    NSString * str = @"www.baidu.com";

    NSError * error;
    BOOL isSucess = [str writeToFile:@"/Users/xujincheng/Desktop/baidu.txt" atomically:YES encoding:NSUTF8StringEncoding error:&error];
    if (isSucess) {
        NSLog(@"写入成功");
    }
    //注意:如果是其他类型需要使用NSData接收
    
    //删除操作如下
    NSFileManager * fm = [[NSFileManager alloc] init];
    BOOL isSucess2 = [fm removeItemAtPath:@"/Users/xujincheng/Desktop/baidu.txt" error:&error];
    if (isSucess2) {
        NSLog(@"删除成功");
    }
    //删除成功后桌面的文件还在,但是打不开了,应该是没刷新吧

当需要把多个NSData数据拼接成一个数据存储的时候,就要想到使用NSMutableData这个类型

 NSString * str1 = @"好好学习";
 NSString * str2 = @"天天向上";

 NSMutableData * muData = [[NSMutableData alloc] init];
 NSData * d1 = [str1 dataUsingEncoding:NSUTF8StringEncoding];
 NSData * d2 = [str2 dataUsingEncoding:NSUTF8StringEncoding];

 //appendData 能够把nsdata对象加入到 muData对象中
 [muData appendData:d1];
 [muData appendData:d2];

 NSString * path =@"/Users/xujincheng/Desktop/baidu.txt";

 BOOL iswriteSucess = [muData writeToFile:path atomically:YES];
 if (iswriteSucess) {
      NSLog(@"写入成功");
 }

运行结果如下, 会把原来的信息都给覆盖掉
iOS-数据存储_第5张图片
结果.png

五. NSFileManager

NSFileManager是iOS的一个文件操作类, 使用的不多, 简单介绍下

为了测试, 我们在沙盒里面创建一些文件和文件夹, 如下
测试.png
一. 文件信息获取
  1. 检查文件是否存在
    NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
    NSString *path=[docPath stringByAppendingPathComponent:@"xujincheng"];
    
    NSFileManager *fm = [[NSFileManager alloc] init];
    BOOL isDirectory ; //是否是文件夹
    BOOL isExists = [fm fileExistsAtPath:path isDirectory:&isDirectory]; //是否存在
    //directory目录
    if (isExists) {
        NSLog(@"存在");
        if (isDirectory) {
            NSLog(@"是文件夹");
        } else {
             NSLog(@"是文件");
        }
    }
    /*
   2019-11-05 14:28:26.838901+0800 归档[87700:7169471] 存在
   2019-11-05 14:28:26.838968+0800 归档[87700:7169471] 是文件
   */
  1. 获取当前路径下的文件/文件夹目录
    //获取当前路径下的文件/文件夹目录
    NSArray * array=[fm contentsOfDirectoryAtPath:docPath error:nil];
    NSLog(@"%@",array);
    /*
     (
     ".DS_Store",
     "测试",
     xujincheng,
     "测试2"
     )
     */
    
  1. 逐级获取所有子集的目录
    //逐级获取所有子集的目录(数组里是子集路径)
    NSError *error;
    NSArray * array2 = [fm subpathsOfDirectoryAtPath:docPath error:&error];
    NSLog(@"%@",array2);
    /*
     (
     ".DS_Store",
     "测试",
     "测试/测试.txt",
     xujincheng,
     "测试2",
     "测试2/.DS_Store",
     "测试2/xujincheng2",
     "测试2/xujincheng",
     "测试2/测试3",
     "测试2/测试3/xujincheng5",
     "测试2/xujincheng1"
     )
     */
    
  1. 获取当前路径下文件/文件夹所有属性的值
    //获取当前路径下文件/文件夹所有属性的值
    NSDictionary * dic = [fm attributesOfItemAtPath:docPath error:&error];
    NSLog(@"%@",dic);
    /*
     {
     NSFileCreationDate = "2019-11-05 02:20:00 +0000";
     NSFileExtendedAttributes =     {
     "com.apple.lastuseddate#PS" = <56ddc05d 00000000 3b0c7f16 00000000>;
     };
     NSFileExtensionHidden = 0;
     NSFileGroupOwnerAccountID = 20;
     NSFileGroupOwnerAccountName = staff;
     NSFileModificationDate = "2019-11-05 02:20:00 +0000";
     NSFileOwnerAccountID = 501;
     NSFilePosixPermissions = 420;
     NSFileReferenceCount = 1;
     NSFileSize = 252;
     NSFileSystemFileNumber = 8613456470;
     NSFileSystemNumber = 16777220;
     NSFileType = NSFileTypeRegular;
     }
     */

    比如获取文件创建日期和大小
    NSDate * date = [dic objectForKey:NSFileCreationDate];
    NSString * size = [dic objectForKey:NSFileSize];
二. 文件操作
逐级创建文件夹, NO表示只能够创建一级目录
BOOL isCreateSuccess = [fm createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error];

创建文件
BOOL isCreateSuccess =  [fm createFileAtPath:pathTo contents:data attributes:nil];

移动目录移动
BOOL isMoveSuccess = [fm moveItemAtPath:path toPath:pathTo error:&error];

删除目录
BOOL isRemoveSuccess = [fm removeItemAtPath:path error:&error];

拷贝文件目录到其他地方
BOOL isCopySuccess = [fm copyItemAtPath:path toPath:pathTo error:&error];

小练习:
将一个目录剪切到另外一个目录

    //1.拿到文件路径
    NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
    NSString *fromPath=[docPath stringByAppendingPathComponent:@"测试2"];
    NSString *toPath=[docPath stringByAppendingPathComponent:@"测试"];
    
    //2.得到目录下面的所有文件
    NSFileManager *fm = [[NSFileManager alloc] init];
    NSArray *subPaths = [fm subpathsAtPath:fromPath];
    
    //3.遍历所有文件,然后执行剪切操作
    //在子线程操作
    NSInteger count = subPaths.count;
    dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
        //3.1 拼接文件的全路径
        NSString *fullPath = [fromPath stringByAppendingPathComponent:subPaths[I]];
        NSString *toFullPath = [toPath stringByAppendingPathComponent:subPaths[I]];
        NSLog(@"%@",fullPath);
        
        //3.2 执行剪切操作
        [fm moveItemAtPath:fullPath toPath:toFullPath error:nil];
    });

运行结果如下图:
iOS-数据存储_第6张图片
文件移动.png

六. KeyChain钥匙串

钥匙串数据存在系统下面的一个数据库里, 所以可以进行应用之间数据交互, 并且应用删除之后数据还在

  1. 增删改查

增:

- (void)saveDataToKeyChain{
    
    NSMutableDictionary *infoDict = [NSMutableDictionary new];
    // 1 存的什么数据类型
    /*
     kSecClassInternetPassword // 互联网密码
     kSecClassGenericPassword // 通用密码
     kSecClassCertificate// 证书
     kSecClassKey  // 密钥
     kSecClassIdentity // 身份ID
    */
    [infoDict setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
    
    // 2 存的什么数据value
    NSString *password = @"abc123";
    [infoDict setObject:[password dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData];
    
    // 3 设置查询的条件 
    [infoDict setObject:@"EOCClassTest" forKey:(id)kSecAttrAccount];
    
    //往系统下面添加一个字典
    OSStatus status = SecItemAdd((CFDictionaryRef)infoDict, NULL);
    if (status == noErr) {
        NSLog(@"success");
    }else{
        NSLog(@"fail: %d", status);
    }
}

删:

- (void)deleteArchData{
    
    NSMutableDictionary *conditionInfo = [NSMutableDictionary new];
    // 1 数据类型
    [conditionInfo setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
    //2 设置条件 (查询)
    [conditionInfo setObject:@"EOCClassTest" forKey:(id)kSecAttrAccount];
    
    OSStatus status = SecItemDelete((CFDictionaryRef)conditionInfo);
    if (status == noErr) {
        NSLog(@"success");
    }else{
        NSLog(@"fail: %d", status);
    } 
}

改:

- (void)updateArch{
    NSMutableDictionary *conditionInfo = [NSMutableDictionary new];
    // 1 数据类型
    [conditionInfo setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
    //2 设置条件 (查询)
    [conditionInfo setObject:@"EOCClassTest" forKey:(id)kSecAttrAccount];

    // 更新的数据
    NSMutableDictionary *newValueDict = [NSMutableDictionary new];
    NSString *newValue = @"987654";
    [newValueDict setObject:[newValue dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData];
    
    OSStatus status = SecItemUpdate((CFDictionaryRef)conditionInfo, (CFDictionaryRef)newValueDict);
    if (status == noErr) {
        NSLog(@"success");
    }else{
        NSLog(@"fail: %d", status);
    }
}

查:

//通过系统提供的接口,操作数据库
- (void)getArchData{
    
    //条件字典
    NSMutableDictionary *conditionInfo = [NSMutableDictionary new];
    // 1 数据类型
    [conditionInfo setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
    //2 设置条件 (查询)
    [conditionInfo setObject:@"EOCClassTest" forKey:(id)kSecAttrAccount];
    // 3 返回的数据格式
    [conditionInfo setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData]; // kSecReturnRef
    
    CFDataRef data = NULL; //返回的值
    OSStatus status = SecItemCopyMatching((CFDictionaryRef)conditionInfo, (CFTypeRef*)&data);
    if (status == noErr) {
        NSLog(@"success");
    }else{
        NSLog(@"fail: %d", status);
    }
    
    NSData *backData = (__bridge NSData*)data;
    NSLog(@"Get:::%s", [backData bytes]);
}

实现如上代码即可实现钥匙串的增删改查, 但是我试了,上面代码不全对

当报错的时候可根据错误码去SecBase.h搜索错误, 常见的错误如下
错误.png
  1. 应用之间数据交互

① 在当前应用存储到钥匙串

- (void)saveDataMultiApp{
    NSMutableDictionary *infoDict = [NSMutableDictionary new];
    // 1 数据类型
    /*
     kSecClassInternetPassword // 互联网密码
     kSecClassGenericPassword // 通用密码
     kSecClassCertificate// 证书
     kSecClassKey  // 密钥
     kSecClassIdentity // 身份ID
     */
    [infoDict setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
    
    // 2 数据value
    NSString *password = @"eocApp123";
    [infoDict setObject:[password dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData];
    
    /*
      跨应用数据
      kSecAttrAccessGroup组的条件key
      1 配置groupkey (机构号 + bundle identifier)
      2 配置一个plist文件(把groupkey放进去, 如下图)
      3 配置工程的 coding entitlment, 如下图
     */
    //机构号就是发布证书里面的用户ID或者组织单位
    [infoDict setObject:@"Y9PY69PRSB.com.test.EocClass" forKey:(id)kSecAttrAccessGroup];
    
    // 3 设置条件 (查询)
    [infoDict setObject:@"EOCClassMutiApp" forKey:(id)kSecAttrAccount];
    
    OSStatus status = SecItemAdd((CFDictionaryRef)infoDict, NULL);
    if (status == noErr) {
        NSLog(@"success");
    }else{
        NSLog(@"fail: %d", status);
    }
}

跨应用数据
kSecAttrAccessGroup组的条件key
1 配置groupkey (机构号 + bundle identifier)机构号就是发布证书里面的用户ID或者组织单位
2 配置一个plist文件(把groupkey放进去, 如下图)
3 配置工程的 coding entitlment, 如下图

添加SysDataSave.plist文件
iOS-数据存储_第7张图片
添加plist文件.png

配置plist文件路径
配置plist文件.png

② 在其他应用读取
我们重新创建一个应用, 将上面的plist文件拖到本项目, 并且配置plist文件路径, 用如下代码读取

- (void)getArchData{
    NSMutableDictionary *conditionInfo = [NSMutableDictionary new];
    // 1 数据类型
    [conditionInfo setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
    //2 设置条件 (查询)
    [conditionInfo setObject:@"EOCClassMutiApp" forKey:(id)kSecAttrAccount];
    // 3 返回的数据格式
    [conditionInfo setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
    
    CFDataRef data = NULL;
    OSStatus status = SecItemCopyMatching((CFDictionaryRef)conditionInfo, (CFTypeRef*)&data);
    if (status == noErr) {
        NSLog(@"success");
    }else{
        NSLog(@"fail: %d", status);
    }
    
    NSData *backData = (__bridge NSData*)data;
    NSLog(@"Get:::%s", [backData bytes]);
}

可以发现, 只要把一个应用的groupkey告诉你, 你就可以读取到它存在钥匙串里面的东西.
这种方式不用走网络. 用的比较少, 作为了解

Demo地址:keyChain

你可能感兴趣的:(iOS-数据存储)