CloudKit实践

CloudKit的应用实践

Posted by Ted on October 18, 2017

最近个人开发了一个保存密码的简单APP,有一个网络备份功能,鉴于功能比较轻便和安全私密性的考虑,采用了CloudKit作为备份服务器的方式进行备份资源。写个博客记录一下应用过程。

一、CloudKit简介

CloudKit,是苹果推出的基于iCloud的一个云端数据存储服务,提供了低成本的云存储并能作为一个后端服务通过用户们的iCloud账号分享其应用数据。

CloudKit主要由两个部分组成:

  1. 一个仪表web页面用于管理公开数据的记录类型。
  2. 一组API接口用于iCloud和设备之间的数据传递。

CloudKit也具有安全性,为用户的私人数据提供了完整的保护。而开发者不仅只能接入自己的数据库,也不允许查看用户的私有数据。

CloudKit适用于那些在服务端计算量不大,却需要使用大量数据的iOS平台独占应用。

二、分类

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、首先先登录iOS开发者网站,在证书处注册一个iCloud的Identifiers

img

2、在Xcode的项目里Capbilities里把iCloud功能打开

img

3、会在项目目录里自动生成一个HTKey.entitlements的文件

img

四、增删改查

1、获取URL,这个URL类似于我们在沙盒里的位置

//获取url
-(NSURL*)getUbiquityContainerUrl:(NSString*)fileName{
    if (!self.myUrl) {
        self.myUrl = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:UbiquityContainerIdentifier];
        if (!self.myUrl) {
            NSLog(@"未开启iCloud功能");
            return nil;
        }
    }
    NSURL *url = [self.myUrl URLByAppendingPathComponent:@"Documents"];
    url = [url URLByAppendingPathComponent:fileName];
    return url;
}

2、上传文档,我们首先创建一个类用于上传下载文档,继承自UIDocument

.h

@interface MyDocument : UIDocument

@property(strong,nonatomic)NSData * data;

@end

.m文件

@implementation MyDocument

//读取icloud数据调用,响应openWithCompletionHandler
- (BOOL)loadFromContents:(id)contents ofType:(nullable NSString *)typeName error:(NSError **)outError __TVOS_PROHIBITED
{
    self.data = [contents copy];
    return true;
}

//保存数据、修改数据到icloud,响应save
- (nullable id)contentsForType:(NSString *)typeName error:(NSError **)outError __TVOS_PROHIBITED
{
    if (!self.data) {
        self.data = [[NSData alloc] init];
    }
    return self.data;
}
@end

3、创建文件并上传

    NSString *fileName =@"back_key";
    NSURL *url = [self getUbiquityContainerUrl:fileName];
    MyDocument *docHandler = [[MyDocument alloc] initWithFileURL:url];
    NSData *back_data = [NSKeyedArchiver archivedDataWithRootObject:app.items];
    docHandler.data = back_data;
    [docHandler saveToURL:url forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
        if (success) {
            NSLog(@"上传成功");
            [SVProgressHUD showSuccessWithStatus:@"上传成功"];
        }
        else{
            NSLog(@"上传失败");
        }
    }];

4、更新文档

    NSString *fileName =@"back_key";
    NSURL *url = [self getUbiquityContainerUrl:fileName];
    MyDocument *doc = [[MyDocument alloc] initWithFileURL:url];
    //文档内容
    NSString*str = @"修改了数据";
    doc.myData = [str dataUsingEncoding:NSUTF8StringEncoding];
    [doc saveToURL:url forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
        if (success) {
            NSLog(@"修改成功");
        }
        else{
            NSLog(@"修改失败");
        }
    }];

5、下载文档

//获取最新数据
-(void)downloadDoc{
    [self.myMetadataQuery setSearchScopes:@[NSMetadataQueryUbiquitousDocumentsScope]];
    [self.myMetadataQuery startQuery];
}

监听下载完成

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(MetadataQueryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:self.myMetadataQuery];

下载成功

//获取成功
-(void)MetadataQueryDidFinishGathering:(NSNotification*)noti{
    NSArray *items = self.myMetadataQuery.results;
    [items enumerateObjectsUsingBlock:^(NSMetadataItem *item, NSUInteger idx, BOOL * _Nonnull stop) {
        NSString *fileName = [item valueForAttribute:NSMetadataItemFSNameKey];
        //读取文件内容
        MyDocument *doc =[[MyDocument alloc] initWithFileURL:[self getUbiquityContainerUrl:fileName]];
        [doc openWithCompletionHandler:^(BOOL success) {
            if (success) {
                NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithData:doc.data];
                AppDelegate *app = (AppDelegate *)[UIApplication sharedApplication].delegate;
                app.items = [NSMutableArray arrayWithArray:array];
                [SVProgressHUD showSuccessWithStatus:@"下载成功"];
            }
        }];
    }];
}