iOS-数据持久化存储

一. 沙盒

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

应⽤沙盒的文件系统⽬录,如下图所示:
沙盒.png

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

1. Documents

默认是备份的(使用itunes或者iCloud会备份的时候)。
大文件尽量不要存储在这个目录下(视频文件)如果不做任何处理,审核,检查出来,就会被拒。

如果不想被拒,下面的操作二选一:

  1. 文件非备份操作设置

因为Documents文件夹默认是备份的,我们需要写以下代码:

// 设置非备份
//#import <sys/xattr.h>
- (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文件夹下的东西默认就不会备份了。

  1. 大文件放到其他目录

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:
Bundle.png

① 主Bundle

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

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

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

//获取资源路径
NSString *imagePath = [[NSBundle mainBundle] pathForResource:@”apple” ofType:@”png”];
//根据路径得到资源
UIImage *appleImage = [[UIImage alloc] initWithContentsOfFile:imagePath];
//代码中的mainBundle类方法用于返回一个代表应用程序包的对象

② 新添加的Bundle

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

//获取Bundle路径
NSString *boudlePath = [[NSBundle mainBundle] pathForResource:@"EOCBundle" ofType:@"bundle"];
//根据Bundle路径获取Bundle
NSBundle *eocBoundle = [NSBundle bundleWithPath:boudlePath];
//根据Bundle获取图片路径
NSString *imagePath = [eocBoundle pathForResource:@"11" ofType:@"png"];
//根据图片路径得到图片资源
_imageView.image = [UIImage imageWithContentsOfFile:imagePath];

上面方式稍微有点麻烦,我们经常这样获取:

UIImage *ima = [UIImage imageNamed:@"StaticDemo2.bundle/美女2.png"];

补充:

项目中黄色文件夹和蓝色文件夹的区别:

  1. 我们项目中一般创建的都是黄色文件夹,黄色的是参与编译的,比如:
    黄色.png
  2. 项目中的蓝色文件夹是资源文件夹,不参与编译,比如:
    蓝色.png

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

① 使用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文件夹
*/

② 使用沙盒根目录拼接

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

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

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

二. NSUserDefaults

  1. 很多iOS应用都支持偏好设置,比如保存用户名、密码、字体大小等设置,iOS提供了一套标准的解决方案来为应用加入偏好设置功能。
  2. 每个应用都有个NSUserDefaults实例,通过它来存取偏好设置,比如,保存用户名、字体大小、是否自动登录。
  3. 它是XML属性列表,属性列表是一种XML格式的文件,拓展名为plist。

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

存储格式:
格式.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中(不推荐)。

三. NSKeyedUnarchiver(归档)

  • 使用plist进行数据存储和读取,只适用于系统自带的一些常用类型才能用,且必须先获取路径相对麻烦。
  • 对于NSUserDefaults,主要用于存储应用的设置信息,而且它的本质还是plist文件,plist都能直接打开,相对不安全。

① 归档一个对象

这两者都有一个致命的缺陷,只能存储常用的类型(plist文件支持的类型)所以我们要介绍归档,归档可以实现把自定义的对象存放在文件中

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

@interface Person : NSObject<NSCoding>
//姓名
@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);

运行后,归档的文件如下:
归档.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(@"写入成功");
}

运行结果如下,会把原来的信息都给覆盖掉:
结果.png

五. NSFileManager

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

为了测试,我们在沙盒里面创建一些文件和文件夹,如下:
测试.png

1. 文件信息获取

① 检查文件是否存在

//directory目录
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]; //是否存在

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] 是文件
*/

② 获取当前路径下的文件/文件夹目录

//获取当前路径下的文件/文件夹目录
NSArray * array=[fm contentsOfDirectoryAtPath:docPath error:nil];
NSLog(@"%@",array);
/*
 (
 ".DS_Store",
 "测试",
 xujincheng,
 "测试2"
 )
 */

③ 逐级获取所有子集的目录

//逐级获取所有子集的目录(数组里是子集路径)
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"
 )
 */

④ 获取当前路径下文件/文件夹所有属性的值

//获取当前路径下文件/文件夹所有属性的值
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];

2. 文件操作

//逐级创建文件夹, 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];
});

运行结果如下图:
文件移动.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

2. 应用之间数据交互

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

- (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文件:
添加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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容

  • iOS应用数据存储的常用方式 XML属性列表(plist)归档2.Preference(偏好设置)3.NSKeye...
    清风沐沐阅读 329评论 0 1
  • 前言 在iOS开发中必不可少的要用到数据存储,数据的处理是iOS开发中的核心技术,适当的对数据进行持久化存储可以实...
    若小北00阅读 8,046评论 6 28
  • 本文将对以下几个模块进行总结 下图是Core Data堆栈的图示,在这里是为了做文章的封面图片,后文会介绍Core...
    MR_THT阅读 1,039评论 0 3
  • 数据存储 iOS应用数据存储的常用方式 - XML属性列表(plist)归档 - Preference(偏好设置)...
    Hevin_Chen阅读 221评论 0 0
  • 夏天的风很温柔,阳光明媚,走在路上,一步一步的走着,不知道走了多远,回头望去,想忘记那个人,那些事,和那段感情。 ...
    王宇智zhi阅读 96评论 0 0