如何实现iCloud中的key-value存储与CloudKit、iCloud Documents的协同开发?

2026-05-23 05:541阅读0评论SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计3088个文字,预计阅读时间需要13分钟。

目录+iCloud开发+iCloud三种存储方式+项目配置+1. iCloud配置+2. 官网配置+3. 本地Xcode配置+注意事项+1. key-value storage+2. 获取默认store+3. 写入数据+4. 读取数据+5. 监听数据变化(多台设备)

目录
  • iCloud开发
    • iCloud三种类型的存储方式
  • 项目配置
    • 1、iCloud 官网配置
    • 2、本地Xcode配置
    • 注意事项
  • 一、key-value storage
    • 1、获取默认store
    • 2、写入数据
    • 3、读取数据
    • 4、监听数据改变(多台设备)
  • 二、CloudKit
    • iCloud官网配置
      • 1、选择某个容器
      • 2、新建Record,类似表名称
      • 3、新建Field,类似表字段
      • 4、进入Data,创建记录
    • 编码开始
      • 1、查询数据
      • 2、新增数据
      • 3、删除数据
      • 4、修改数据
      • 5、监听iCloud账户状态
    • CloudKit 知识扫盲
  • 三、iCloud Documents
      • 1、Xcode配置
      • 2、自定义UIDocument
      • 3、保存图片
      • 4、下载图片
      • 5、Mac版代码稍微有点区别
      • 6、监听数据改变

iCloud开发
  • 使用iCloud的开发的前提是要有开发者账号,个人或企业均可。
iCloud三种类型的存储方式 类型 说明 key-value storage 键值对的存储服务,用于一些简单的数据存储 iCloud Documents 文档存储服务,用于将文件保存到iCloud中 CloudKit 云端数据库服务 项目配置
  • 这些是基本配置,三种方式都需要这些配置。
1、iCloud 官网配置
  • 配置iCloud Containers

  • 创建支持iCloud的Apple ID,并关联上相应的iCloud容器。

  • 输入Identifier即可创建完毕

2、本地Xcode配置
  • 添加CloudKit框架到项目
  • Xcode配置信息: 选择项目->targets->Capabilities->iCloud->打开开关
  • 1、勾选自己要开启的Services
  • 2、选择对应的Containers,可以使用默认,也可以指定固定的
  • 3、观察steps是否全部success
  • 4、修改entitlements
注意事项
  • demo是iOS和macos数据同步,所以配置稍微复杂点
  • 如果只是iOS设备间进行同步,不用修改Containers,使用默认即可,第四步不用修改
  • 其中iCloud Key-Value Store 默认是用team id和bundle identifier做标识,因为mac和ios的bundle identifier不一致,所以要手动指定为统一的
一、key-value storage
  • 一般用于同步少量数据或者进行一些配置性质的数据同步,使用简单。
  • 使用NSUbiquitousKeyValueStore对象进行数据读写
1、获取默认store

// 获取默认的store,这就是在xxx.entitlements里配置的`iCloud Key-Value Store` self.keyValueStore = [NSUbiquitousKeyValueStore defaultStore]; 2、写入数据

NSLog(@"写入iCloud数据:%zd",self.number); [self.keyValueStore setLongLong:self.number forKey:@"number"]; // 同步数据,避免冲突 [self.keyValueStore synchronize]; 3、读取数据

// 在获取到store后,读取iCloud数据 self.number = [self.keyValueStore longLongForKey:@"number"]; 4、监听数据改变(多台设备)

  • 需要实时知道一些配置的变更,特别是在你有多台设备时(如同时拥有iPhone和iPad)

// 添加监听 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dataChanged:) name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:nil]; - (void)dataChanged:(NSNotification *)noti{ // 监听到keyvalue值改变就会触发这个通知 NSLog(@"keyvalue改变了:%@",noti); if ([noti.userInfo[NSUbiquitousKeyValueStoreChangedKeysKey] containsObject:@"number"]) { self.number = [noti.object longLongForKey:@"number"]; NSLog(@"keyvalue改变了:%zd",self.number); self.myLabel.stringValue = [NSString stringWithFormat:@"%zd",self.number]; } }

  • iPhone上改变,mac上监听
二、CloudKit iCloud官网配置
  • iCloud官网配置地址
1、选择某个容器

2、新建Record,类似表名称
  • 需要打开recordName的索引,这样客户端才能查询数据
3、新建Field,类似表字段

  • Filed 类型
4、进入Data,创建记录
  • 创建记录后才能在客户端进行增删查改



编码开始
  • 以上配置完毕可尝试在客户端进行增删查改。
  • 初始化容器对象

// 初始化容器对象 self.container = [CKContainer containerWithIdentifier:ContainerID]; 1、查询数据

  • 判断iCloud账户状态 accountStatusWithCompletionHandler:
  • 获取私有数据库对象 weakSelf.container.privateCloudDatabase
  • 查询数据 performQuery: inZoneWithID:completionHandler:

if(self.container){ // 访问私有数据库 __weak typeof(self) weakSelf = self; [weakSelf.container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError * _Nullable error) { // 只有登录iCloud才能读取 if (accountStatus == CKAccountStatusAvailable) { // 获取私有数据库实例 CKDatabase *db = weakSelf.container.privateCloudDatabase; CKQuery *query = [[CKQuery alloc] initWithRecordType:RecordType predicate:[NSPredicate predicateWithValue:YES]]; // 查询数据 [db performQuery:query inZoneWithID:nil completionHandler:^(NSArray<CKRecord *> * _Nullable results, NSError * _Nullable error) { if(!error){ weakSelf.preOrders = [NSMutableArray arrayWithArray:results]; NSLog(@"%@",results); dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf.tableView reloadData]; }); }else{ NSLog(@"Error:%@",error); } }]; }else { NSLog(@"登录iCloud错误"); } }]; }else { NSLog(@"连接iCloud错误"); }

  • 返回CKRecord类型的数据,可以通过objectForKey方法直接读取

<CKRecord: 0x101813050; recordID=FF125857-926B-4DC5-B972-9E6A6502B5A5:(_defaultZone:__defaultOwner__), recordChangeTag=jzjms8b2, values={\n amount = 9144;\n time = \"2021-08-20 09:33:45 +0000\";\n}, recordType=Water> NSDate *time = [ck objectForKey:@"time"]; NSInteger count = [ck objectForKey:@"amount"]; 2、新增数据

  • 判断iCloud账户状态 accountStatusWithCompletionHandler:

  • 获取私有数据库对象 weakSelf.container.privateCloudDatabase

  • 创建Record CKRecord *record = [[CKRecord alloc] initWithRecordType:RecordType];

  • 保存数据 saveRecord: completionHandler:

    if(self.container){ // 1 访问私有数据库 __weak typeof(self) weakSelf = self; [self.container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError * _Nullable error) { // 1.1 只有登录iCloud才能读取 if (accountStatus == CKAccountStatusAvailable) { // 1.2 获取私有数据库实例 CKDatabase *db = weakSelf.container.privateCloudDatabase; // 新增数据 CKRecord *record = [[CKRecord alloc] initWithRecordType:RecordType]; record[@"time"] = [NSDate date]; record[@"amount"] = @(arc4random()%10000); // 1.3 保存数据 [db saveRecord:record completionHandler:^(CKRecord * _Nullable record, NSError * _Nullable error) { if(error) { NSLog(@"%@", error); } else { NSLog(@"Saved successfully:%@",record); dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf queryAction:nil]; }); } }]; }else { NSLog(@"登录iCloud错误"); } }]; }

3、删除数据
  • 判断iCloud账户状态 accountStatusWithCompletionHandler:
  • 获取私有数据库对象 weakSelf.container.privateCloudDatabase
  • 查询Record是否存在 fetchRecordWithID: completionHandler:
  • 删除数据 deleteRecordWithID: completionHandler:

if(self.container){ // 访问私有数据库 __weak typeof(self) weakSelf = self; [weakSelf.container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError * _Nullable error) { // 只有登录iCloud才能读取 if (accountStatus == CKAccountStatusAvailable) { // 获取私有数据库实例 CKDatabase *db = weakSelf.container.privateCloudDatabase; [db fetchRecordWithID:record.recordID completionHandler:^(CKRecord * _Nullable record, NSError * _Nullable error) { if(error) { NSLog(@"%@", error); } else { NSLog(@"查询成功:%@",record); [db deleteRecordWithID:record.recordID completionHandler:^(CKRecordID * _Nullable recordID, NSError * _Nullable error) { if(error) { NSLog(@"%@", error); } else { NSLog(@"删除成功:%@",record); dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf.preOrders removeObjectAtIndex:indexPath.row]; [weakSelf.tableView reloadData]; }); } }]; } }]; }else { NSLog(@"登录iCloud错误"); } }]; }else { NSLog(@"连接iCloud错误"); } 4、修改数据

  • 判断iCloud账户状态 accountStatusWithCompletionHandler:
  • 获取私有数据库对象 weakSelf.container.privateCloudDatabase
  • 查询Record是否存在 fetchRecordWithID: completionHandler:
  • 保存数据 saveRecord: completionHandler:

if(self.container){ // 访问私有数据库 __weak typeof(self) weakSelf = self; NSInteger count = [weakSelf.countTextField.text integerValue]; [weakSelf.container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError * _Nullable error) { // 只有登录iCloud才能读取 if (accountStatus == CKAccountStatusAvailable) { // 获取私有数据库实例 CKDatabase *db = weakSelf.container.privateCloudDatabase; [db fetchRecordWithID:weakSelf.record.recordID completionHandler:^(CKRecord * _Nullable record, NSError * _Nullable error) { if(error) { NSLog(@"%@", error); } else { NSLog(@"查询成功:%@",record); record[@"amount"] = @(count); [db saveRecord:record completionHandler:^(CKRecord * _Nullable record, NSError * _Nullable error) { if(error) { NSLog(@"%@", error); } else { NSLog(@"更改成功:%@",record); dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:@"refreshUI" object:nil]; [weakSelf.navigationController popViewControllerAnimated:YES]; }); } }]; } }]; }else { NSLog(@"登录iCloud错误"); } }]; }else { NSLog(@"连接iCloud错误"); } 5、监听iCloud账户状态

// 账户信息状态改变了会触发这个信息,可以尝试刷新数据 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dataChanged:) name:CKAccountChangedNotification object:nil]; - (void)dataChanged:(NSNotification *)noti{ NSLog(@"账户信息状态改变了:%@",noti); [self queryAction:nil]; } CloudKit 知识扫盲

  • CKContainer 容器,或者沙盒,每个应用只能访问自己的容器。
  • CKDatabase 顾名思义,数据库了,包含私有数据库和公有数据库,用户只能访问自己的私有数据库,一些不敏感的数据也可以存储在公有数据库中。
  • CKRecord 数据记录,keyvalue形式存储的,存储一些基本类型(NSString,NSNumber,NSData,NSDate,CLLocation,CKAsset,CKReference等)
  • CKRecordZone 类似分区,是用来保存Record的。所有的Record都是保存在这里,应用有一个默认的zone,也可以自定义zone。
  • CKAsset 文件存储记录
  • CKQuery 数据库查询对象,指定查询条件进行数据查询
三、iCloud Documents 1、Xcode配置
  • 允许你把一份文档上传到iCloud中,然后其他设备再同步app上传的文档。

2、自定义UIDocument
  • 首先继承UIDocument,实现自己的方法,做好NSData数据的转换

#import "MyDocument.h" @implementation MyDocument - (instancetype)initWithFileURL:(NSURL *)url image:(UIImage *)image { if (self = [super initWithFileURL:url]) { _myImage = image; } return self; } // 写入数据前 - (nullable id)contentsForType:(NSString *)typeName error:(NSError **)outError{ // 只能返回NSData 或者 NSFileWrapper ,所以这里要转换图片 return UIImageJPEGRepresentation(_myImage, 0.7); } // 读取数据后 - (BOOL)loadFromContents:(id)contents ofType:(nullable NSString *)typeName error:(NSError **)outError { if ([contents isKindOfClass:[NSData class]]) { // 如果是NSData,还要转换成图片 _myImage = [UIImage imageWithData:contents]; } return YES; } @end 3、保存图片

  • 文件名可以随机生成
  • 查询时进行模糊查询即可全部查出来
  • 保存方法 saveToURL: forSaveOperation: completionHandler:

- (void)saveWithImage:(UIImage *)image{ if(self.baseURL){ // UIImage *image = [UIImage imageNamed:@"1"]; self.localImageView.image = image; NSURL *bgURL = [self.baseURL URLByAppendingPathComponent:@"100JZPlg6M1DQht3xU.png"]; MyDocument *bgImg = [[MyDocument alloc] initWithFileURL:bgURL image:image]; [bgImg saveToURL:bgURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) { if (success) { NSLog(@"同步成功!"); } else { NSLog(@"同步失败, 可以记录到本地等待下一次重新同步"); } }]; }else{ NSLog(@"连接iCloud错误"); } } 4、下载图片

- (IBAction)downloadClick { // 进行文档同步 if(self.baseURL){ __weak typeof(self) weakSelf = self; __block NSMetadataQuery *query = [[NSMetadataQuery alloc] init]; // 查询数据范围 query.searchScopes = @[NSMetadataQueryUbiquitousDataScope]; // 查询条件 NSMetadataItemFSNameKey 按照文件名搜索 // query.predicate = [NSPredicate predicateWithFormat:@"%K == '100JZPlg6M1DQht3xU.png'", NSMetadataItemFSNameKey]; // 模糊查询使用 * query.predicate = [NSPredicate predicateWithFormat:@"%K like '*JZPlg6M1DQht3xU.png'", NSMetadataItemFSNameKey]; // 监听查询结果 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserverForName:NSMetadataQueryDidFinishGatheringNotification object:query queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { NSLog(@"Note:%@",note); // query.results 查询结果数组,如果模糊匹配可能有多个 if (query.results.count > 0) { NSURL *fileURL = [(NSMetadataItem *)query.results.firstObject valueForAttribute:NSMetadataItemURLKey]; //加载背景图片 MyDocument *bgImage = [[MyDocument alloc] initWithFileURL:fileURL image:nil]; [bgImage openWithCompletionHandler:^(BOOL success) { if (success) { NSLog(@"下载成功!"); weakSelf.backgroungImageView.image = bgImage.myImage; }else{ NSLog(@"下载失败"); } }]; } // 查询完毕,关闭 [query stopQuery]; }]; // 开启查询 [query startQuery]; } } 5、Mac版代码稍微有点区别

  • 自定义NSDocument

#import "MyDocument.h" @implementation MyDocument // 读取数据后 - (BOOL)readFromURL:(NSURL *)url ofType:(NSString *)typeName error:(NSError * _Nullable __autoreleasing *)outError { NSImage *im = [[NSImage alloc] initWithContentsOfURL:url]; if (im) { self.myImage = im; return YES; } return NO; } // 写入数据前 - (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError { // 只能返回NSData 或者 NSFileWrapper ,所以这里要转换图片 NSData *data = self.myImage.TIFFRepresentation; return data; } - (instancetype)initWithFileURL:(NSURL *)url image:(NSImage *)image { if (self = [super initWithContentsOfURL:url ofType:@"png" error:nil]) { _myImage = image; } return self; } @end

  • 保存图片

- (void)saveWithImage:(NSImage *)image{ if(self.baseURL){ self.localImageView.image = image; NSURL *bgURL = [self.baseURL URLByAppendingPathComponent:@"100JZPlg6M1DQht3xU.png"]; MyDocument *bgImg = [[MyDocument alloc] initWithFileURL:bgURL image:image]; [bgImg saveToURL:bgURL ofType:@"png" forSaveOperation:NSSaveOperation completionHandler:^(NSError * _Nullable errorOrNil) { if (!errorOrNil) { NSLog(@"同步成功!"); } else { NSLog(@"同步失败, 可以记录到本地等待下一次重新同步:%@",errorOrNil); } }]; }else{ NSLog(@"连接iCloud错误"); } }

  • 下载图片

- (IBAction)downloadClick:(NSButton *)btn { // 进行文档同步 if(self.baseURL){ __weak typeof(self) weakSelf = self; __block NSMetadataQuery *query = [[NSMetadataQuery alloc] init]; // 查询数据范围 query.searchScopes = @[NSMetadataQueryUbiquitousDataScope]; // 查询条件 NSMetadataItemFSNameKey 按照文件名搜索 // query.predicate = [NSPredicate predicateWithFormat:@"%K == '100JZPlg6M1DQht3xU.png'", NSMetadataItemFSNameKey]; // 模糊查询使用 * query.predicate = [NSPredicate predicateWithFormat:@"%K like '*JZPlg6M1DQht3xU.png'", NSMetadataItemFSNameKey]; // 监听查询结果 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserverForName:NSMetadataQueryDidFinishGatheringNotification object:query queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { NSLog(@"Note:%@",note); // query.results 查询结果数组,如果模糊匹配可能有多个 if (query.results.count > 0) { NSURL *fileURL = [(NSMetadataItem *)query.results.firstObject valueForAttribute:NSMetadataItemURLKey]; //加载背景图片 MyDocument *bgImage = [[MyDocument alloc] initWithFileURL:fileURL image:nil]; if([bgImage readFromURL:fileURL ofType:@"png" error:nil]){ NSLog(@"下载成功!"); weakSelf.localImageView.image = bgImage.myImage; } else{ NSLog(@"下载失败"); } } // 查询完毕,关闭 [query stopQuery]; }]; // 开启查询 [query startQuery]; } } 6、监听数据改变

  • 监听通知 NSMetadataQueryDidFinishGatheringNotification
  • 查询里面的 [query stopQuery]; 需要注释掉

// 监听数据改变 [center addObserverForName:NSMetadataQueryDidFinishGatheringNotification object:query queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { if (query.results.count > 0) { NSURL *fileURL = [(NSMetadataItem *)query.results.firstObject valueForAttribute:NSMetadataItemURLKey]; //加载背景图片 MyDocument *bgImage = [[MyDocument alloc] initWithFileURL:fileURL image:nil]; if([bgImage readFromURL:fileURL ofType:@"png" error:nil]){ NSLog(@"下载成功!"); weakSelf.localImageView.image = bgImage.myImage; } else{ NSLog(@"下载失败"); } } }];

本文共计3088个文字,预计阅读时间需要13分钟。

目录+iCloud开发+iCloud三种存储方式+项目配置+1. iCloud配置+2. 官网配置+3. 本地Xcode配置+注意事项+1. key-value storage+2. 获取默认store+3. 写入数据+4. 读取数据+5. 监听数据变化(多台设备)

目录
  • iCloud开发
    • iCloud三种类型的存储方式
  • 项目配置
    • 1、iCloud 官网配置
    • 2、本地Xcode配置
    • 注意事项
  • 一、key-value storage
    • 1、获取默认store
    • 2、写入数据
    • 3、读取数据
    • 4、监听数据改变(多台设备)
  • 二、CloudKit
    • iCloud官网配置
      • 1、选择某个容器
      • 2、新建Record,类似表名称
      • 3、新建Field,类似表字段
      • 4、进入Data,创建记录
    • 编码开始
      • 1、查询数据
      • 2、新增数据
      • 3、删除数据
      • 4、修改数据
      • 5、监听iCloud账户状态
    • CloudKit 知识扫盲
  • 三、iCloud Documents
      • 1、Xcode配置
      • 2、自定义UIDocument
      • 3、保存图片
      • 4、下载图片
      • 5、Mac版代码稍微有点区别
      • 6、监听数据改变

iCloud开发
  • 使用iCloud的开发的前提是要有开发者账号,个人或企业均可。
iCloud三种类型的存储方式 类型 说明 key-value storage 键值对的存储服务,用于一些简单的数据存储 iCloud Documents 文档存储服务,用于将文件保存到iCloud中 CloudKit 云端数据库服务 项目配置
  • 这些是基本配置,三种方式都需要这些配置。
1、iCloud 官网配置
  • 配置iCloud Containers

  • 创建支持iCloud的Apple ID,并关联上相应的iCloud容器。

  • 输入Identifier即可创建完毕

2、本地Xcode配置
  • 添加CloudKit框架到项目
  • Xcode配置信息: 选择项目->targets->Capabilities->iCloud->打开开关
  • 1、勾选自己要开启的Services
  • 2、选择对应的Containers,可以使用默认,也可以指定固定的
  • 3、观察steps是否全部success
  • 4、修改entitlements
注意事项
  • demo是iOS和macos数据同步,所以配置稍微复杂点
  • 如果只是iOS设备间进行同步,不用修改Containers,使用默认即可,第四步不用修改
  • 其中iCloud Key-Value Store 默认是用team id和bundle identifier做标识,因为mac和ios的bundle identifier不一致,所以要手动指定为统一的
一、key-value storage
  • 一般用于同步少量数据或者进行一些配置性质的数据同步,使用简单。
  • 使用NSUbiquitousKeyValueStore对象进行数据读写
1、获取默认store

// 获取默认的store,这就是在xxx.entitlements里配置的`iCloud Key-Value Store` self.keyValueStore = [NSUbiquitousKeyValueStore defaultStore]; 2、写入数据

NSLog(@"写入iCloud数据:%zd",self.number); [self.keyValueStore setLongLong:self.number forKey:@"number"]; // 同步数据,避免冲突 [self.keyValueStore synchronize]; 3、读取数据

// 在获取到store后,读取iCloud数据 self.number = [self.keyValueStore longLongForKey:@"number"]; 4、监听数据改变(多台设备)

  • 需要实时知道一些配置的变更,特别是在你有多台设备时(如同时拥有iPhone和iPad)

// 添加监听 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dataChanged:) name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:nil]; - (void)dataChanged:(NSNotification *)noti{ // 监听到keyvalue值改变就会触发这个通知 NSLog(@"keyvalue改变了:%@",noti); if ([noti.userInfo[NSUbiquitousKeyValueStoreChangedKeysKey] containsObject:@"number"]) { self.number = [noti.object longLongForKey:@"number"]; NSLog(@"keyvalue改变了:%zd",self.number); self.myLabel.stringValue = [NSString stringWithFormat:@"%zd",self.number]; } }

  • iPhone上改变,mac上监听
二、CloudKit iCloud官网配置
  • iCloud官网配置地址
1、选择某个容器

2、新建Record,类似表名称
  • 需要打开recordName的索引,这样客户端才能查询数据
3、新建Field,类似表字段

  • Filed 类型
4、进入Data,创建记录
  • 创建记录后才能在客户端进行增删查改



编码开始
  • 以上配置完毕可尝试在客户端进行增删查改。
  • 初始化容器对象

// 初始化容器对象 self.container = [CKContainer containerWithIdentifier:ContainerID]; 1、查询数据

  • 判断iCloud账户状态 accountStatusWithCompletionHandler:
  • 获取私有数据库对象 weakSelf.container.privateCloudDatabase
  • 查询数据 performQuery: inZoneWithID:completionHandler:

if(self.container){ // 访问私有数据库 __weak typeof(self) weakSelf = self; [weakSelf.container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError * _Nullable error) { // 只有登录iCloud才能读取 if (accountStatus == CKAccountStatusAvailable) { // 获取私有数据库实例 CKDatabase *db = weakSelf.container.privateCloudDatabase; CKQuery *query = [[CKQuery alloc] initWithRecordType:RecordType predicate:[NSPredicate predicateWithValue:YES]]; // 查询数据 [db performQuery:query inZoneWithID:nil completionHandler:^(NSArray<CKRecord *> * _Nullable results, NSError * _Nullable error) { if(!error){ weakSelf.preOrders = [NSMutableArray arrayWithArray:results]; NSLog(@"%@",results); dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf.tableView reloadData]; }); }else{ NSLog(@"Error:%@",error); } }]; }else { NSLog(@"登录iCloud错误"); } }]; }else { NSLog(@"连接iCloud错误"); }

  • 返回CKRecord类型的数据,可以通过objectForKey方法直接读取

<CKRecord: 0x101813050; recordID=FF125857-926B-4DC5-B972-9E6A6502B5A5:(_defaultZone:__defaultOwner__), recordChangeTag=jzjms8b2, values={\n amount = 9144;\n time = \"2021-08-20 09:33:45 +0000\";\n}, recordType=Water> NSDate *time = [ck objectForKey:@"time"]; NSInteger count = [ck objectForKey:@"amount"]; 2、新增数据

  • 判断iCloud账户状态 accountStatusWithCompletionHandler:

  • 获取私有数据库对象 weakSelf.container.privateCloudDatabase

  • 创建Record CKRecord *record = [[CKRecord alloc] initWithRecordType:RecordType];

  • 保存数据 saveRecord: completionHandler:

    if(self.container){ // 1 访问私有数据库 __weak typeof(self) weakSelf = self; [self.container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError * _Nullable error) { // 1.1 只有登录iCloud才能读取 if (accountStatus == CKAccountStatusAvailable) { // 1.2 获取私有数据库实例 CKDatabase *db = weakSelf.container.privateCloudDatabase; // 新增数据 CKRecord *record = [[CKRecord alloc] initWithRecordType:RecordType]; record[@"time"] = [NSDate date]; record[@"amount"] = @(arc4random()%10000); // 1.3 保存数据 [db saveRecord:record completionHandler:^(CKRecord * _Nullable record, NSError * _Nullable error) { if(error) { NSLog(@"%@", error); } else { NSLog(@"Saved successfully:%@",record); dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf queryAction:nil]; }); } }]; }else { NSLog(@"登录iCloud错误"); } }]; }

3、删除数据
  • 判断iCloud账户状态 accountStatusWithCompletionHandler:
  • 获取私有数据库对象 weakSelf.container.privateCloudDatabase
  • 查询Record是否存在 fetchRecordWithID: completionHandler:
  • 删除数据 deleteRecordWithID: completionHandler:

if(self.container){ // 访问私有数据库 __weak typeof(self) weakSelf = self; [weakSelf.container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError * _Nullable error) { // 只有登录iCloud才能读取 if (accountStatus == CKAccountStatusAvailable) { // 获取私有数据库实例 CKDatabase *db = weakSelf.container.privateCloudDatabase; [db fetchRecordWithID:record.recordID completionHandler:^(CKRecord * _Nullable record, NSError * _Nullable error) { if(error) { NSLog(@"%@", error); } else { NSLog(@"查询成功:%@",record); [db deleteRecordWithID:record.recordID completionHandler:^(CKRecordID * _Nullable recordID, NSError * _Nullable error) { if(error) { NSLog(@"%@", error); } else { NSLog(@"删除成功:%@",record); dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf.preOrders removeObjectAtIndex:indexPath.row]; [weakSelf.tableView reloadData]; }); } }]; } }]; }else { NSLog(@"登录iCloud错误"); } }]; }else { NSLog(@"连接iCloud错误"); } 4、修改数据

  • 判断iCloud账户状态 accountStatusWithCompletionHandler:
  • 获取私有数据库对象 weakSelf.container.privateCloudDatabase
  • 查询Record是否存在 fetchRecordWithID: completionHandler:
  • 保存数据 saveRecord: completionHandler:

if(self.container){ // 访问私有数据库 __weak typeof(self) weakSelf = self; NSInteger count = [weakSelf.countTextField.text integerValue]; [weakSelf.container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError * _Nullable error) { // 只有登录iCloud才能读取 if (accountStatus == CKAccountStatusAvailable) { // 获取私有数据库实例 CKDatabase *db = weakSelf.container.privateCloudDatabase; [db fetchRecordWithID:weakSelf.record.recordID completionHandler:^(CKRecord * _Nullable record, NSError * _Nullable error) { if(error) { NSLog(@"%@", error); } else { NSLog(@"查询成功:%@",record); record[@"amount"] = @(count); [db saveRecord:record completionHandler:^(CKRecord * _Nullable record, NSError * _Nullable error) { if(error) { NSLog(@"%@", error); } else { NSLog(@"更改成功:%@",record); dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:@"refreshUI" object:nil]; [weakSelf.navigationController popViewControllerAnimated:YES]; }); } }]; } }]; }else { NSLog(@"登录iCloud错误"); } }]; }else { NSLog(@"连接iCloud错误"); } 5、监听iCloud账户状态

// 账户信息状态改变了会触发这个信息,可以尝试刷新数据 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dataChanged:) name:CKAccountChangedNotification object:nil]; - (void)dataChanged:(NSNotification *)noti{ NSLog(@"账户信息状态改变了:%@",noti); [self queryAction:nil]; } CloudKit 知识扫盲

  • CKContainer 容器,或者沙盒,每个应用只能访问自己的容器。
  • CKDatabase 顾名思义,数据库了,包含私有数据库和公有数据库,用户只能访问自己的私有数据库,一些不敏感的数据也可以存储在公有数据库中。
  • CKRecord 数据记录,keyvalue形式存储的,存储一些基本类型(NSString,NSNumber,NSData,NSDate,CLLocation,CKAsset,CKReference等)
  • CKRecordZone 类似分区,是用来保存Record的。所有的Record都是保存在这里,应用有一个默认的zone,也可以自定义zone。
  • CKAsset 文件存储记录
  • CKQuery 数据库查询对象,指定查询条件进行数据查询
三、iCloud Documents 1、Xcode配置
  • 允许你把一份文档上传到iCloud中,然后其他设备再同步app上传的文档。

2、自定义UIDocument
  • 首先继承UIDocument,实现自己的方法,做好NSData数据的转换

#import "MyDocument.h" @implementation MyDocument - (instancetype)initWithFileURL:(NSURL *)url image:(UIImage *)image { if (self = [super initWithFileURL:url]) { _myImage = image; } return self; } // 写入数据前 - (nullable id)contentsForType:(NSString *)typeName error:(NSError **)outError{ // 只能返回NSData 或者 NSFileWrapper ,所以这里要转换图片 return UIImageJPEGRepresentation(_myImage, 0.7); } // 读取数据后 - (BOOL)loadFromContents:(id)contents ofType:(nullable NSString *)typeName error:(NSError **)outError { if ([contents isKindOfClass:[NSData class]]) { // 如果是NSData,还要转换成图片 _myImage = [UIImage imageWithData:contents]; } return YES; } @end 3、保存图片

  • 文件名可以随机生成
  • 查询时进行模糊查询即可全部查出来
  • 保存方法 saveToURL: forSaveOperation: completionHandler:

- (void)saveWithImage:(UIImage *)image{ if(self.baseURL){ // UIImage *image = [UIImage imageNamed:@"1"]; self.localImageView.image = image; NSURL *bgURL = [self.baseURL URLByAppendingPathComponent:@"100JZPlg6M1DQht3xU.png"]; MyDocument *bgImg = [[MyDocument alloc] initWithFileURL:bgURL image:image]; [bgImg saveToURL:bgURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) { if (success) { NSLog(@"同步成功!"); } else { NSLog(@"同步失败, 可以记录到本地等待下一次重新同步"); } }]; }else{ NSLog(@"连接iCloud错误"); } } 4、下载图片

- (IBAction)downloadClick { // 进行文档同步 if(self.baseURL){ __weak typeof(self) weakSelf = self; __block NSMetadataQuery *query = [[NSMetadataQuery alloc] init]; // 查询数据范围 query.searchScopes = @[NSMetadataQueryUbiquitousDataScope]; // 查询条件 NSMetadataItemFSNameKey 按照文件名搜索 // query.predicate = [NSPredicate predicateWithFormat:@"%K == '100JZPlg6M1DQht3xU.png'", NSMetadataItemFSNameKey]; // 模糊查询使用 * query.predicate = [NSPredicate predicateWithFormat:@"%K like '*JZPlg6M1DQht3xU.png'", NSMetadataItemFSNameKey]; // 监听查询结果 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserverForName:NSMetadataQueryDidFinishGatheringNotification object:query queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { NSLog(@"Note:%@",note); // query.results 查询结果数组,如果模糊匹配可能有多个 if (query.results.count > 0) { NSURL *fileURL = [(NSMetadataItem *)query.results.firstObject valueForAttribute:NSMetadataItemURLKey]; //加载背景图片 MyDocument *bgImage = [[MyDocument alloc] initWithFileURL:fileURL image:nil]; [bgImage openWithCompletionHandler:^(BOOL success) { if (success) { NSLog(@"下载成功!"); weakSelf.backgroungImageView.image = bgImage.myImage; }else{ NSLog(@"下载失败"); } }]; } // 查询完毕,关闭 [query stopQuery]; }]; // 开启查询 [query startQuery]; } } 5、Mac版代码稍微有点区别

  • 自定义NSDocument

#import "MyDocument.h" @implementation MyDocument // 读取数据后 - (BOOL)readFromURL:(NSURL *)url ofType:(NSString *)typeName error:(NSError * _Nullable __autoreleasing *)outError { NSImage *im = [[NSImage alloc] initWithContentsOfURL:url]; if (im) { self.myImage = im; return YES; } return NO; } // 写入数据前 - (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError { // 只能返回NSData 或者 NSFileWrapper ,所以这里要转换图片 NSData *data = self.myImage.TIFFRepresentation; return data; } - (instancetype)initWithFileURL:(NSURL *)url image:(NSImage *)image { if (self = [super initWithContentsOfURL:url ofType:@"png" error:nil]) { _myImage = image; } return self; } @end

  • 保存图片

- (void)saveWithImage:(NSImage *)image{ if(self.baseURL){ self.localImageView.image = image; NSURL *bgURL = [self.baseURL URLByAppendingPathComponent:@"100JZPlg6M1DQht3xU.png"]; MyDocument *bgImg = [[MyDocument alloc] initWithFileURL:bgURL image:image]; [bgImg saveToURL:bgURL ofType:@"png" forSaveOperation:NSSaveOperation completionHandler:^(NSError * _Nullable errorOrNil) { if (!errorOrNil) { NSLog(@"同步成功!"); } else { NSLog(@"同步失败, 可以记录到本地等待下一次重新同步:%@",errorOrNil); } }]; }else{ NSLog(@"连接iCloud错误"); } }

  • 下载图片

- (IBAction)downloadClick:(NSButton *)btn { // 进行文档同步 if(self.baseURL){ __weak typeof(self) weakSelf = self; __block NSMetadataQuery *query = [[NSMetadataQuery alloc] init]; // 查询数据范围 query.searchScopes = @[NSMetadataQueryUbiquitousDataScope]; // 查询条件 NSMetadataItemFSNameKey 按照文件名搜索 // query.predicate = [NSPredicate predicateWithFormat:@"%K == '100JZPlg6M1DQht3xU.png'", NSMetadataItemFSNameKey]; // 模糊查询使用 * query.predicate = [NSPredicate predicateWithFormat:@"%K like '*JZPlg6M1DQht3xU.png'", NSMetadataItemFSNameKey]; // 监听查询结果 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserverForName:NSMetadataQueryDidFinishGatheringNotification object:query queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { NSLog(@"Note:%@",note); // query.results 查询结果数组,如果模糊匹配可能有多个 if (query.results.count > 0) { NSURL *fileURL = [(NSMetadataItem *)query.results.firstObject valueForAttribute:NSMetadataItemURLKey]; //加载背景图片 MyDocument *bgImage = [[MyDocument alloc] initWithFileURL:fileURL image:nil]; if([bgImage readFromURL:fileURL ofType:@"png" error:nil]){ NSLog(@"下载成功!"); weakSelf.localImageView.image = bgImage.myImage; } else{ NSLog(@"下载失败"); } } // 查询完毕,关闭 [query stopQuery]; }]; // 开启查询 [query startQuery]; } } 6、监听数据改变

  • 监听通知 NSMetadataQueryDidFinishGatheringNotification
  • 查询里面的 [query stopQuery]; 需要注释掉

// 监听数据改变 [center addObserverForName:NSMetadataQueryDidFinishGatheringNotification object:query queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { if (query.results.count > 0) { NSURL *fileURL = [(NSMetadataItem *)query.results.firstObject valueForAttribute:NSMetadataItemURLKey]; //加载背景图片 MyDocument *bgImage = [[MyDocument alloc] initWithFileURL:fileURL image:nil]; if([bgImage readFromURL:fileURL ofType:@"png" error:nil]){ NSLog(@"下载成功!"); weakSelf.localImageView.image = bgImage.myImage; } else{ NSLog(@"下载失败"); } } }];