一、什么是cloudKit
移动开发中,网络请求数据是日常中用到的,我们习惯把一些用户改动的数据存在服务器,以便下次请求使用。或者开发者方通过服务器将编辑的数据发送给用户。但是这一切都建立在我们的APP拥有自己的服务器之上。如果没有服务器的情况下我们的某些开发就变得很难进行,比如,公司没有服务器,确需要你做出一个用户系统,这显然令人头痛,因为这几乎是一个不可能完成的任务(有人说,扯淡吧,公司会没有服务器? 呵呵,我公司就没有啊)。然而苹果提供了一些便利,虽然不能完全替代后端,但是在一定程度上可以解决数据的云端存储需求,没错就是本文要讲的:CloudKit。每个开发者在使用CloudKit之后,苹果会为其提供一个不小的dashboard作为云端数据的存储地,CloudKit 提供了一套API用于在dashboard中存取数据。简直太方便了啊有米有。
关于CloudKit的详细说明,我建议大家看看这里。
二、如何使用cloudKit
使用之前,我们先了解下关于CloudKit的一些基本数据类型。
CloudKit 基础对象类型
CloudKit 的基础对象类型有 7 种。这些对象类型可能和你在其他编程领域了解的类似对象类型稍有差别。
CKContainer
: Containers 就像应用运行的沙盒一样,一个应用只能访问自己沙盒中的内容而不能访问其他应用的。Containers 就是最外层容器,每个应用有且仅有一个属于自己的 container。(事实上,经过开发者授权配置 CloudKit Dashboard 之后,一个应用也可以访问其他应用的 container。)CKDatabase
: Database 即数据库,私有数据库用来存储敏感信息,比如说用户的性别年龄等,用户只能访问自己的私有数据库。应用也有一个公开的数据库来存储公共信息,例如你在构建一个根据地理位置签到的应用,那么地理位置信息就应该存储在公共数据库里以便所有用户都能访问到。CKRecord
: 即数据库中的一条数据记录。CloudKit 使用 record 通过 k/v 结构来存储结构化数据。关于键值存储,目前值的架构支持 NSString、NSNumber、NSData、NSDate、CLLocation,和 CKReference、CKAsset,以及存储以上数据类型的数组。CKRecordZone
: Record 不是以零散的方式存在于 database 之中的,它们位于 record zones 里。每个应用都有一个 default record zone,你也可以有自定义的 record zone。CKRecordIdentifier
: 是一条 record 的唯一标识,用于确定该 record 在数据库中的唯一位置。CKReference
: Reference 很像 RDBMS 中的引用关系。还是以地理位置签到应用为例,每个地理位置可以包含很多用户在该位置的签到,那么位置与签到之间就形成了这样一种包含式的从属关系。CKAsset
: 即资源文件,例如二进制文件。还是以签到应用为例,用户签到时可能还包含一张照片,那么这张照片就会以 asset 形式存储起来。
本文就一个简单的例子说说使用cloudKit。假如我们需要做一个用户信息的存储系统。
使用CloudKit 的环境配置
在使用CloudKit之前,我们需要先在Xcode 中做一些配置。 (本文基于Xcode8)
- 在target -> capabilities 中找到 iColod选项。
- 打开iCloud的控制开关。
- 在展开的框中做如下设置
其中在containers中的 “ cloudKit dashBoard ”按钮可以直接进入到我们在icloud上的存储空间。 这里要确保APP中使用的开发者账号是实际存在的 。进入网页之后登录开发者账号登录。
登录成功会出现这个画面:
这里我已经存了一部分数据。 存进去之后还是很直观的,但是看不到存储的属性对应的内容,我也还在研究中。
使用CloudHit 保存数据
如果我们需要保存一个包含了用户的 账户、密码、用户名字、电话号码、邮箱、用户头像等数据的对象。 我们创建一个用户的属性类 UserInfoModel。
#import <Foundation/Foundation.h> @interface UserInfoModel : NSObject /** 账户名称 */ @property (nonatomic,strong)NSString *userAccout; /** 用户名字 */ @property (nonatomic,strong) NSString *userName; /** 用户头像 */ @property (nonatomic,strong) UIImage *userAvtar; /** 用户手机号码 */ @property (nonatomic,strong) NSString *userPhoneNum; /** 用户邮箱号 */ @property (nonatomic,strong) NSString *userEmail; /** 用户密码 */ @property (nonatomic,strong) NSString *userPassword; @end
在保存数据之前,我们为了之后方便查询,可以将某一项作为唯一标示(这要求标示不会经常被修改),比如在这里我们选择账户名,当然你也可以选择其他任意的字符,关键是之后的查找要有依据。 接下来就开始进行保存了。
- (void)saveWithModel:(UserInfoModel*)userInfoModel{ //因为账户名不变的 以帐户名做微ID最好不过了 CKRecordID *postrecordID = [[CKRecordID alloc]initWithRecordName:userInfoModel.userAccout]; CKRecord *postRecrod = [[CKRecord alloc] initWithRecordType:userInfoModel.userAccout recordID:postrecordID]; //将用户类的属性和属性值打包成一个字典 其中属性对应key 属性值对应Value 因为属性中有一栏是图片类,CloudKit不支持直接对图片进行保存,但是可以转换成NSdata,这洋就可以进行保存了. 这里说明一下 cloudKit的提交 只接受NSString、NSNumber、NSData、CLLocation,和 CKReference、CKAsset 等直接的存储, 其它的需要 NSMutableDictionary *dic = [[NSMutableDictionary alloc]init]; NSMutableArray *propArr = [self getAllProp:[userInfoModel class]]; //这里使用getAllProp 在下面贴出 for (NSString *prop in propArr) { if([[userInfoModel valueForKey:prop] isKindOfClass:[UIImage class]]){ // 图片特殊情况另外处理 如果实别的不符合存储的也同样需要处理 UIImage *image = [userInfoModel valueForKey:prop]; postRecrod[prop] = [NSData dataWithData:UIImagePNGRepresentation(image)]; //record可以像字典一样进行数据的收纳 }else{ postRecrod[prop] = [userInfoModel valueForKey:prop]; } } //用户信息 提交到 云 [[[CKContainer defaultContainer] privateCloudDatabase] saveRecord:postRecrod completionHandler:^(CKRecord *savedPlace, NSError *error) { if(savedPlace){ DLog(@"%@",savedPlace); //成功 打印存储的内容 }else{ DLog(@"%@",error); //失败 打印错误 } }]; }
- (NSMutableArray *)getAllProp:(Class)cls{ // 获取当前类的所有属性 unsigned int count;// 记录属性个数 objc_property_t *properties = class_copyPropertyList(cls, &count); // 遍历 NSMutableArray *mArray = [NSMutableArray array]; for (int i = 0; i < count; i++) { // objc_property_t 属性类型 objc_property_t property = properties[i]; // 获取属性的名称 C语言字符串 const char *cName = property_getName(property); // 转换为Objective C 字符串 NSString *name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding]; [mArray addObject:name]; } return mArray; }
使用CloudHit 查询数据
- (void )cloudGetUserInfoWithUseraccout:(NSString *)userAccout Succeed:(void(^)(UserInfoModel* ))succeed failed:(void(^)(NSError *))failed{ UserInfoModel *model = [[UserInfoModel alloc]init]; if(userAccout){ CKRecordID *postrecordID = [[CKRecordID alloc]initWithRecordName:userAccout]; [[[CKContainer defaultContainer] privateCloudDatabase] fetchRecordWithID:postrecordID completionHandler:^(CKRecord * _Nullable record, NSError * _Nullable error) { // handle errors here if(error){ if(failed){ failed(error); } }else{ //说明查询成功 if(succeed){ //已经获取到了存入的数据, 并经过转换存入了字典 dic 将字典中的键值对赋给一个类对应的属性 同理因为其中有一个图片 所以需要做一个NSdata的转换 NSMutableArray *mArray = [self getAllProp:[model class]]; for (NSString *prop in mArray) { //这里如果不是在后台人为添加的数据 不会出现没有对应的属性的情况 但是为了保险起见。在UserInfoModel 重写 setVale:forUndefinedKey方法 id info = [record valueForKey:prop]; if(![info isKindOfClass:[NSData class]]){ // [model setValue:[dic valueForKey:prop] forKey:prop]; [model setValue:record[prop] forKey:prop]; }else{ UIImage *image = [UIImage imageWithData:info]; [model setValue:image forKey:prop]; } } succeed(model); //回调获取到的模型 } } } ]; } }
使用CloudHit 删除数据
- (void)cloudDeleteModelWithModel:(UserInfoModel *)userInfoModel{ //之前也说了在存储的时候, 我们操纵cloud上的数据都是通过record ID来实现的, 所以record ID的名字应该是一个不经常改变的属性, 这里用的就是用户的账户名 CKRecordID *postrecordID = [[CKRecordID alloc]initWithRecordName:userInfoModel.userAccout]; [[[CKContainer defaultContainer] privateCloudDatabase] deleteRecordWithID:postrecordID completionHandler:^(CKRecordID * _Nullable recordID, NSError * _Nullable error) { if(!error){ //删除成功 }else{ DLog(@"删除失败%@",error); //打印错误 } }]; }
以上就是cloudKit的简单使用。通过这个,当需要存储一些比较简单的数据还是可以满足的。
PS:我也刚开始用,发现了一个小问题, 单次存储的数据不能太大(大小没有测试,大概是2M左右吧),不然会提示失败。 所以有的数据过大,可能需要经过拆分,或者图片过大需要经过压缩才能进行存储。