主要讲解iCloud工程的创建和CloudKit的使用
CloudKit是什么
- 云端数据库,存储数据,提供简单的增删改查功能
- 特点是方便简单,适合不懂后台的个人开发者.
- 安全,毕竟是苹果自家的产品.不需要复杂的登录认证体系.
- 目前苹果允许你使用 CloudKit 存储 10 GB 资源,100 M 数据库存储,每天 2 GB 流量;当你的用户数量增加的时候,这些免费额度也相应地增加到 1 PB 存储、10 TB 数据库存储,以及每天 200 TB 流量,几乎用不完.你感觉不够用的时候,你的用户量已经很庞大了....
- 由两部分组成
- 一个仪表web页面,用于管理公开数据的记录类型.
- 一组API接口,用于iCloud和设备之间的数据传递.
一、开发者账号中启用iCloud服务
1.选择要添加iCloud功能的appid,勾选iCloud,选择Include CloudKit support (requires Xcode 6),后点击Edit,选择需要额外添加的Container。
2.每个bundleid下本身会有一个Container,如果需要额外的Container,可以通过iCloud Containers创建
3.然后可以在Edit里选择需要的Container后,点击continue 以及Assign就完成了添加。
->>
二、在Xcode中启用iCloud
1.必须在Xcode中添加账号,选择对应的Team
2.在Capabilities里,打开iCloud开关,并勾选CloudKit,如果需要额外的容器,勾选Specify custom containers,选择额外的容器,这个容器也可以在其他app中添加。
三、关于 CloudKit Dashboard
1.可以点击上图中的CloudKit Dashboard按钮进入,也可以在https://developer.apple.com里进入
2.选择该Team下其中一个容器
其中主要使用的是Record Types
一个Record Type用来定义一个单独的记录(可以理解为一个数据模型),相当于存储数据的模板,和数据库的表结构类似
PUBLIC DATA 和 PRIVATE DATA 就是你保存数据的地方,开发者可以查看所有的共享数据,但是只能看到自己的私密数据,无法看到用户的私密数据;这里没有显示PRIBATE DATA,其结果和PUBLIC DATA是一样的;
可以在Dashboard中添加记录、添加记录模型等
支持的数据类型
四、代码中使用
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 形式存储起来。
1.增加一条记录
//获取默认的容器
CKContainer *container = [CKContainer defaultContainer];
// 如果是自定义的容器
// CKContainer *shareContainer = [CKContainer containerWithIdentifier:ContainerID];
CKDatabase *database;
if(isPublic)
{
database = container.publicCloudDatabase;//公共数据库
}
else
{
database = container.privateCloudDatabase;//私有数据库
}
//创建主键ID 这个ID到时查找有用到
CKRecordID *noteId = [[CKRecordID alloc] initWithRecordName:recordID];
//创建CKRecord 保存数据
CKRecord *noteRecord = [[CKRecord alloc] initWithRecordType:@"User" recordID:noteId];
//设置数据
[noteRecord setObject:name forKey:@"name"];
[noteRecord setObject:password forKey:@"password"];
//保存操作
[database saveRecord:noteRecord completionHandler:^(CKRecord *_Nullablerecord,NSError *_Nullableerror) {
if(!error)
{
NSLog(@"保存成功");
}
else
{
NSLog(@"保存失败: %@",error);
}
}];
注意 :
默认用户只能只读数据库,要添加修改则需要登录icloud账户
公有数据库所有的用户(安装app的用户,不是指开发者)都可以访问,私有的只能当前用户能访问.
-
如果用户没有登录,提醒用户登录icloud
[[CKContainer defaultContainer] accountStatusWithCompletionHandler:^(CKAccountStatusaccountStatus,NSError *_Nullableerror) { if(accountStatus ==CKAccountStatusNoAccount) { handler(NO); } else { //登录过了 handler(YES); } }];
2.获取一条记录
CKRecordID *noteId = [[CKRecordID alloc]initWithRecordName:recordID];
CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
//通过主键ID查找记录
[publicDatabase fetchRecordWithID:noteId completionHandler:^(CKRecord *_Nullablerecord,NSError *_Nullableerror) {
if(!error)
{
NSLog(@"查询成功: %@",record);
}
else
{
NSLog(@"查询失败: %@",error);
}
}];
3.查询多条记录
CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name != %@",@"xiaowang"];
CKQuery *query = [[CKQuery alloc] initWithRecordType:recordTypeName predicate:predicate];
NSSortDescriptor *firstDescriptor = [[NSSortDescriptor alloc] initWithKey:@"gender" ascending:NO];
NSSortDescriptor *secondDescriptor = [[NSSortDescriptor alloc] initWithKey:@"age" ascending:NO];
query.sortDescriptors = @[firstDescriptor,secondDescriptor];
//通过谓词查找记录
[publicDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray *_Nullableresults,NSError *_Nullableerror) {
if(!error)
{
NSLog(@"results: %@",results);
}
else
{
NSLog(@"查询失败: %@",error);
}
}];
4.更新一条记录
CKRecordID *noteId = [[CKRecordID alloc] initWithRecordName:recordID];
CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
//先找到记录,再修改记录
[publicDatabase fetchRecordWithID:noteId completionHandler:^(CKRecord *_Nullablerecord,NSError *_Nullableerror) {
if(!error)
{
[recordsetObject:@"123456789" forKey:@"password"];
[recordsetObject:@"m" forKey:@"gender"];
[recordsetObject:@20 forKey:@"age"];
//修改后保存记录
[database saveRecord:record completionHandler:^(CKRecord *_Nullablerecord,NSError *_Nullableerror) {
if(!error)
{
NSLog(@"修改成功 %@",record);
}
else
{
NSLog(@"修改失败: %@",error);
}
}];
}
else
{
NSLog(@"找不到该记录,查询失败: %@",error);
}
}];
5.删除一条记录
CKRecordID *noteId = [[CKRecordID alloc] initWithRecordName:recordID];
CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
[publicDatabase deleteRecordWithID:noteId completionHandler:^(CKRecordID *_NullablerecordID,NSError *_Nullableerror) {
if(!error)
{
NSLog(@"删除成功");
}
else
{
NSLog(@"删除失败: %@",error);
}
}];
6.保存大文件
CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
CKRecordID *noteId = [[CKRecordID alloc] initWithRecordName:recordID];
CKRecord *noteRecord = [[CKRecord alloc]initWithRecordType:@"User" recordID:noteId];
NSString *path = [[NSBundle mainBundle] pathForResource:@"lcz" ofType:@"jpg"];
CKAsset *asset = [[CKAsset alloc] initWithFileURL:[NSURL fileURLWithPath:path]];
[noteRecord setObject:name forKey:@"name"];
[noteRecord setObject:password forKey:@"password"];
[noteRecord setObject:asset forKey:@"userImage"];
[publicDatabase saveRecord:noteRecord completionHandler:^(CKRecord *_Nullablerecord,NSError *_Nullableerror) {
if(!error)
{
NSLog(@"保存成功");
}
else
{
NSLog(@"保存失败: %@",error);
}
}];
7.添加地理位置
__weak typeof(self) weakSelf = self;
CLGeocoder *geocoder = [CLGeocoder new];
[geocoder geocodeAddressString:@"北京" completionHandler:^(NSArray *_Nullableplacemarks,NSError *_Nullableerror) {
if(!error)
{
if(placemarks.count>0)
{
CLPlacemark *placemark = placemarks[0];
NSLog(@"%@",placemark.location);
weakSelf.person.location = placemark.location;
[weakSelf saveRecordWithPublic:YES andKey:@"location" andObject:weakSelf.person.location andRecordType:@"Person" andRecordID:@"sunjie2"];
}
}
}];
8.添加引用(外键)
- (void)addReferenceWithPublic:(BOOL)isPublic
action:(CKReferenceAction)action
andReferenceKey:(NSString*)key
andSourceRecordID:(NSString*)sourceRecordID
andTargetRecordID:(NSString*)targetRecordID
{
CKRecordID *noteID = [[CKRecordID alloc]initWithRecordName:targetRecordID];
CKReference *reference = [[CKReference alloc]initWithRecordID:noteID action:action];
CKContainer *container = [CKContainer defaultContainer];
CKDatabase *database;
if(isPublic)
{
database = container.publicCloudDatabase;
}
else
{
database = container.privateCloudDatabase;
}
CKRecordID *sourceRecordId = [[CKRecordID alloc]initWithRecordName:sourceRecordID];
[database fetchRecordWithID:sourceRecordId completionHandler:^(CKRecord *_Nullablerecord,NSError *_Nullableerror) {
if(!error)
{
[record setObject:reference forKey:key];
[database saveRecord:record completionHandler:^(CKRecord *_Nullablerecord,NSError *_Nullableerror) {
if(!error)
{
NSLog(@"保存成功");
self.person.workN = reference;
}
else
{
NSLog(@"保存失败: %@",error);
}
}];
}
}];
}
9.查询引用的记录
//拿到引用的id
CKRecordID *recordID = reference.recordID;
//根据id查询
[database fetchRecordWithID:recordID completionHandler:^(CKRecord *record,NSError *error) {
if(error)
{
//错误处理
NSLog(@"查询失败%@",error);
}
else
{
// 查询成功
NSLog(@"查询成功%@",record);
}
}];
10.批操作处理
CKFetchRecordsOperation;
CKModifyRecordsOperation;
CKQueryOperation;
CKDatabaseOperation;
CKModifyBadgeOperation;
CKOperation;
CKSubscriptionOptions;
CKModifySubscriptionsOperation;
CKFetchSubscriptionsOperation;
- (void)fetchOperationWithRecordID:(NSArray*)fetchRecordIDs
{
CKFetchRecordsOperation *fetchRecordsOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs:fetchRecordIDs];
fetchRecordsOperation.perRecordCompletionBlock = ^(CKRecord *record,CKRecordID *recordID,NSError *error) {
if(error)
{
//错误处理
NSLog(@"查询失败%@",error);
}
else
{
// 查询成功
NSLog(@"查询成功%@",record);
}
};
fetchRecordsOperation.fetchRecordsCompletionBlock= ^(NSDictionary*recordsByRecordID,NSError*error) {
if(error)
{
//错误处理
NSLog(@"查询失败%@",error);
}
else
{
// 查询成功
NSLog(@"查询成功%@",recordsByRecordID);
}
};
fetchRecordsOperation.database = [[CKContainer defaultContainer] publicCloudDatabase];
[fetchRecordsOperation start];
}
11.添加订阅和通知
options参数的可能值是:
CKSubscriptionOptionsFiresOnRecordCreation,
CKSubscriptionOptionsFiresOnRecordDeletion,
CKSubscriptionOptionsFiresOnRecordUpdate,
CKSubscriptionOptionsFiresOnce.
因为options参数是一个位掩码,可以订阅的改变的类型的任何组合。
例如,您可以通过CKSubscriptionOptionsFiresOnRecordCreation| CKSubscriptionOptionsFiresOnRecordUpdate作为选项:参数来接收所有新数据的通知。
- (void)addSubscriptionAndNotificationsWithRecordID:(NSString*)recordID AndRecordType:(NSString*)recordType
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@",@"Lu"];
//创建一个订阅
CKSubscription*subscription = [[CKSubscription alloc]
initWithRecordType:recordType
predicate:predicate
options:CKSubscriptionOptionsFiresOnRecordCreation];
CKNotificationInfo *notificationInfo = [CKNotificationInfo new];
notificationInfo.alertLocalizationKey = @"订阅新推送";
notificationInfo.shouldBadge=YES;
subscription.notificationInfo= notificationInfo;
CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
[publicDatabase saveSubscription:subscription
completionHandler:^(CKSubscription *subscription,NSError *error) {
if(!error)
{
NSLog(@"订阅成功%@",subscription);
}
else
{
NSLog(@"订阅失败%@",error);
}
}];
}