iOS CloudKit的使用

主要讲解iCloud工程的创建和CloudKit的使用


CloudKit是什么

  • 云端数据库,存储数据,提供简单的增删改查功能
  • 特点是方便简单,适合不懂后台的个人开发者.
  • 安全,毕竟是苹果自家的产品.不需要复杂的登录认证体系.
  • 目前苹果允许你使用 CloudKit 存储 10 GB 资源,100 M 数据库存储,每天 2 GB 流量;当你的用户数量增加的时候,这些免费额度也相应地增加到 1 PB 存储、10 TB 数据库存储,以及每天 200 TB 流量,几乎用不完.你感觉不够用的时候,你的用户量已经很庞大了....
  • 由两部分组成
  1. 一个仪表web页面,用于管理公开数据的记录类型.
  2. 一组API接口,用于iCloud和设备之间的数据传递.

一、开发者账号中启用iCloud服务

1.选择要添加iCloud功能的appid,勾选iCloud,选择Include CloudKit support (requires Xcode 6),后点击Edit,选择需要额外添加的Container。
42B967C8-332B-4259-A0F5-DEC606A1E3B7.png
2.每个bundleid下本身会有一个Container,如果需要额外的Container,可以通过iCloud Containers创建
E6670FF0-5D68-4E7C-AB3F-EEAA001E977D.png
3.然后可以在Edit里选择需要的Container后,点击continue 以及Assign就完成了添加。
18BB7039-E95E-4A07-BFA1-A960BBF20096.png

->>

32CBA831-1CD4-47FE-A542-CD1C546E406A.png

二、在Xcode中启用iCloud

1.必须在Xcode中添加账号,选择对应的Team
7B55F20E-9685-4EA8-AE91-17997179A68C.png
2.在Capabilities里,打开iCloud开关,并勾选CloudKit,如果需要额外的容器,勾选Specify custom containers,选择额外的容器,这个容器也可以在其他app中添加。
503D2F6C-0972-48A7-A381-7FCA60CC25CB.png

三、关于 CloudKit Dashboard

1.可以点击上图中的CloudKit Dashboard按钮进入,也可以在https://developer.apple.com里进入
572E8EC0-2D44-4B46-BA79-F8DD6ECBC75F.png
2.选择该Team下其中一个容器
403D8483-B2E3-4188-8E2F-5E6E388BBB38.png
D9CEE71F-8AC5-4781-AF6A-DDFBE137DA6C.png

其中主要使用的是Record Types

一个Record Type用来定义一个单独的记录(可以理解为一个数据模型),相当于存储数据的模板,和数据库的表结构类似

PUBLIC DATA 和 PRIVATE DATA 就是你保存数据的地方,开发者可以查看所有的共享数据,但是只能看到自己的私密数据,无法看到用户的私密数据;这里没有显示PRIBATE DATA,其结果和PUBLIC DATA是一样的;

可以在Dashboard中添加记录、添加记录模型等

492E3956-9141-4AB3-8F2C-D49C985604C7.png
D6619556-DE0D-470F-A3E0-3F267EB66E8B.png

支持的数据类型

四、代码中使用

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<CKRecord*>  *_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<CLPlacemark*>  *_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<CKRecordID*>*)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);
                      }

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

推荐阅读更多精彩内容