CloudKit Web Services

允许用户把重要的事情记录在云端数据库中,来演示 CloudKit JS 的特性。
CloudKit JS 支持以下浏览器
Safari、FirFox、Chrome、IE、Edge
有趣的是,它还支持 node, 意味着你可以通过自己中间层来获取请求,并展示结果给自己的API接口。

集成

  • 设置好bundleID 和 team
  • 导入CloudKit框架
  • 在 XCode 中的 Capabilities 选项卡中找到 iCloud 选项,并打开开关:

这里写图片描述
- 如果报错可以直接点击fix修复就可以了.或者在开发者中心配置appid(打开icloud,并手动添加容器),并且生成新的调试/发布证书,导入到本地.
这里写图片描述
- 关于共享容器,共享容器就是在同一个开发者不同app之间共享数据.在开发者中心 iCloud Containers这里添加新容器.

这里写图片描述
这里写图片描述
- 在上图选择sepcify custom containers,点击加好旁边的刷新按钮,就会出现新添加的容器.这个容器也可在其他app中被添加.
- 也可以直接点击加号添加新容器,系统会自动添加iCloud前缀
- 注意创建后的容器不能够删除,创建之前要想好,切记.
- 使用支持的数据类型
这里写图片描述
增加一条记录

//创建索引ID
    CKRecordID *artworkRecordID = [[CKRecordID alloc] initWithRecordName:@"115"];

    //创建一条记录,type就是模型,与CloudKit 管理后台的Record types 一致,
    //如果后台没有,会自动添加相应的模型或者字段.
    CKRecord *artworkRecord = [[CKRecord alloc] initWithRecordType:@"Artwork" recordID:artworkRecordID];

    //设置模型的数据,和字典用法几乎一样
    artworkRecord[@"title" ] = @"MacKerricher State Park";
    artworkRecord[@"artist"] = @"Mei Chen";
    artworkRecord[@"address"] = @"Fort Bragg, CA";

    //公有数据库,我建议写成宏
    CKContainer *myContainer = [CKContainer defaultContainer];
    CKDatabase  *publicDatabase = [myContainer publicCloudDatabase];

    //私有数据库
    CKContainer *myContainer = [CKContainer defaultContainer];
    CKDatabase  *privateDatabase = [myContainer privateCloudDatabase];

    //自定义的容器,比如上图中的共享容器,需要id标识
    CKContainer *shareContainer = [CKContainer containerWithIdentifier:@"iCloud.com.example.ajohnson.GalleryShared"];

    //添加一条记录(这里是添加到公有数据库里面)
    [publicDatabase saveRecord:artworkRecord completionHandler:^(CKRecord *artworkRecord, NSError *error){
        if (!error) {
            //写入成功
        }
        else {
            //写入失败的处理
        }
    }];

注意:

默认用户只能只读数据库,要添加修改则需要登录icloud账户

公有数据库所有的用户(安装app的用户,不是指开发者)都可以访问,私有的只能当前用户能访问.

如果用户没有登录,提醒用户登录icloud

[[CKContainer defaultContainer] accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError* error) {
        if (accountStatus == CKAccountStatusNoAccount) {
            UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"尚未登录iCloud" message:nil preferredStyle:UIAlertControllerStyleAlert];

            [alert addAction:[UIAlertAction actionWithTitle:@"确定"
                                                      style:UIAlertActionStyleCancel
                                                    handler:nil]];

            dispatch_async(dispatch_get_main_queue(), ^{
                [self presentViewController:alert animated:YES completion:nil];
            });
        }
        else {
          //登录过了
        }
    }];

获取一条记录

CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
CKRecordID *artworkRecordID = [[CKRecordID alloc] initWithRecordName:@"115"];
[publicDatabase fetchRecordWithID:artworkRecordID completionHandler:^(CKRecord *artworkRecord, NSError *error) {
   if (error) {
     //处理错误
   }
   else {
     // 成功获取到数据
   }
}];

修改记录

// 先获取记录
CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
CKRecordID *artworkRecordID = [[CKRecordID alloc] initWithRecordName:@"115"];
[publicDatabase fetchRecordWithID:artworkRecordID completionHandler:^(CKRecord *artworkRecord, NSError *error) {
   if (error) {
      // 获取失败
   }
   else {
      //修改记录
      NSDate *date = artworkRecord[@"date"];
      artworkRecord[@"date"] = [date dateByAddingTimeInterval:30.0 * 60.0];
      //保存修改后的记录
      [publicDatabase saveRecord:artworkRecord completionHandler:^(CKRecord *savedRecord, NSError *saveError) {

      }];
   }
}];

按照条件查询
查询title是 Santa Cruz Mountains 的记录

CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"title = %@", @"Santa Cruz Mountains"];
CKQuery *query = [[CKQuery alloc] initWithRecordType:@"Artwork" predicate:predicate];
[publicDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
    if (error) {
        //处理错误
    }
    else {
        // 查询成功
    }
}];

查询条件如下:摘自官方文档
这里写图片描述
存储大文件(资源文件,如图片)

   //本地文件的URL
   NSURL *resourceURL = [NSURL fileURLWithPath:@"…"];
   if (resourceURL){
      CKAsset *asset = [[CKAsset alloc] initWithFileURL:resourceURL];
      artworkRecord[@"image"] = asset;
     //保存
   }

添加地理位置

CLGeocoder *geocoder = [CLGeocoder new];
[geocoder geocodeAddressString:artwork[kArtworkAddressKey] completionHandler:^(NSArray *placemark, NSError *error){
   if (!error) {
      if (placemark.count > 0){
         CLPlacemark *placement = placemark[0];
         artworkRecord[kArtworkLocationKey] = placement.location;
      }
   }
   else {

   }
   // 保存到数据库
}];

根据地理位置查询记录

//公有数据库 
CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];

// 创建一个地理位置
CLLocation *fixedLocation = [[CLLocation alloc] initWithLatitude:37.7749300 longitude:-122.4194200];
//半径,单位是米
CGFloat radius = 100; // meters
//查找距离fixedLocation 不到100米的记录
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"distanceToLocation:fromLocation:(location, %@) < %f", fixedLocation, radius];

// 创建一个查询
CKQuery *query = [[CKQuery alloc] initWithRecordType:@"Artwork" predicate:predicate];

// 执行查询
[publicDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
   if (error) {
      //错误处理
   }
   else {
      // 查询成功
   }
}];

添加引用,类似于外键
artist 引用了 artistRecordID这条记录

CKRecordID *artistRecordID = [[CKRecordID alloc] initWithRecordName:@"Mei Chen"];

CKReference *artistReference = [[CKReference alloc] initWithRecordID:artistRecordID action:CKReferenceActionNone];

CKRecord *artworkRecord;
…
artworkRecord[@"artist"] = artistReference;

查询引用
查询当前记录的引用

CKRecord *artworkRecord;
…
CKReference *referenceToArtist = artworkRecord[@"artist"];

//拿到引用的id
CKRecordID *artistRecordID = artistReference.recordID;

//根据id查询
[publicDatabase fetchRecordWithID:artistRecordID completionHandler:^(CKRecord *artistRecord, NSError *error) {
    if (error) {
        //错误处理
    }
    else {
        // 查询成功
    }
}];

查询artistRecordID的引用者(可以有很多个引用者)

NSPredicate *predicate = [NSPredicate predicateWithFormat:@“artist = %@”, artistRecordID];

//引用者类型为Artwork
CKQuery *query = [[CKQuery alloc] initWithRecordType:@“Artwork” predicate:predicate];

CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
[publicDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
    if (error) {
        // 错误处理
    }
    else {
        // 查询成功
    }
}];

批处理操作

CKFetchRecordsOperation *fetchRecordsOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs:fetchRecordIDs];

fetchRecordsOperation.perRecordCompletionBlock = ^(CKRecord *record, CKRecordID *recordID, NSError *error) {
    if (error) {

    }
    else {

    }
};

fetchRecordsOperation.fetchRecordsCompletionBlock = ^(NSDictionary *recordsByRecordID, NSError *error) {
    if (error) {

    }
    else {

    }
};

fetchRecordsOperation.database = [[CKContainer defaultContainer] publicCloudDatabase];
[fetchRecordsOperation start];

订阅 & 推送功能

CKRecordID *artistRecordID = [[CKRecordID alloc] initWithRecordName:@"Mei Chen"];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"artist = %@", artistRecordID];

//创建一个订阅
CKSubscription *subscription = [[CKSubscription alloc]
                                    initWithRecordType:@"Artwork"
                                    predicate:predicate
                                    options:CKSubscriptionOptionsFiresOnRecordCreation];

CKNotificationInfo *notificationInfo = [CKNotificationInfo new];
notificationInfo.alertLocalizationKey = @"New artwork by your favorite artist.";
notificationInfo.shouldBadge = YES;

subscription.notificationInfo = notificationInfo;

CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
    [publicDatabase saveSubscription:subscription
                    completionHandler:^(CKSubscription *subscription, NSError *error) {
                        if (error)

                    }
     ];

注册通知

  UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert categories:nil];
  [application registerUserNotificationSettings:notificationSettings];
  [application registerForRemoteNotifications];

处理通知

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
CKNotification *cloudKitNotification = [CKNotification notificationFromRemoteNotificationDictionary:userInfo];
NSString *alertBody = cloudKitNotification.alertBody;
if (cloudKitNotification.notificationType == CKNotificationTypeQuery) {
 CKRecordID *recordID = [(CKQueryNotification *)cloudKitNotification recordID];
}
}
  • 然后在后台修改一条记录试试.

别忘了最后一步,部署到线上.

Paste_Image.png

第一步是打开 iClound的 developer dashboard。在这里设置应用的细节,创建 record type, 建立安全规则,填写数据等等。你可以在这个页面了解更多关于 dashboard 的内容https://icloud.developer.apple.com/dashboard
创建一个叫做 CloudNotes的应用,把设置都默认。

创建了应用后,我们需要具体指明它的 record type. 这个应用仅存储简单的笔记,有一个标题和内容。选择左栏 Schema 下的 Record Types, 你可以看到,User 的记录类型已经存在了,这是默认生成的。

点击添加按钮“+” ,创建一个新的 record type,命名为 CloudNote. 这个 record 用来存储我们的数据。

这里写图片描述

现在需要给 record 添加字段了,添加 title 和 content 这两个字段,都设置为 String 类型。这是目前我们仅需要设置的数据结构。

接下来,我们添加一条记录,以便在网页上可以展示和检索到东西。在左栏的菜单中,选择 Public Data 下的 “Default Zone”。所有我们要在这个应用中使用的数据都是公共的。在真正的项目中,你可能希望把用户的数据存在单独的私有数据中,但为了简洁,这个示例中我们没有添加安全和授权方面的规则。
这里写图片描述

点击添加按钮,会出现添加的页面,你填写完毕后点击保存按钮,数据就会保存在CloudKit 中了。

现在我们的CloudKit中已经有了一些数据,让我们写段JS代码展示一下他吧。

JS 应用的架构
我们这个应用仅有一个页面(index.html),包含了外链的 JavaScript 文件。用来请求和存储 CloudKit 数据。为了帮助展示数据,我们引入了Knockout JS。它可以用来简化数据的绑定和展示,确保当数据源变化是,UI可以自动被刷新。同时我们还引入了 bootstrap ,省去我们自己去写 css 样式。

下面是所有的外链引用。

<html>
<head>
  <title>iOS9 Day by Day - CloudKit Web Services Example</title>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.3.0/knockout-min.js"></script>
  <script type="text/javascript" src="cloudNotes.js"></script>
  <script src="https://cdn.apple-cloudkit.com/ck/1/cloudkit.js" async></script>
</head>

让我们来看一下 CloudNotes.js,看他是如何从CloudKit获取数据的。

在请求数据之前,我们首先要等待CloudKit API的加载。我们添加下面的代码到window 的eventListener中。它可以监听到 cloudkitloaded事件。

window.addEventListener('cloudkitloaded', function() {

当CloudKit对象加载成功后,你需要设置他的identifier,environment 和 API token。

CloudKit.configure({
    containers: [{
        containerIdentifier: 'iCloud.com.shinobicontrols.CloudNotes',
        apiToken: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
        environment: 'development'
    }]
});

现在我们会到 CloudKit的 Dashboard 来生成一个 API token。在左栏选择Admin下的 API Tokens,然后点击添加,给API token取一个名字,然后把对应的值复制到上面的代码中。
这里写图片描述

在我的代码中,我引用了 Knockout JS 来绑定 model 到 HTML 上。 我创建了一个 CloudNotesViewModel,负责管理页面。它包括一个数组,里面包含了所有的笔记数据。同时包括了保存新笔记,查询笔记,以及在有无权限的情况下的两种展示效果。

在视图模型能够调用这些方法之前,首先我们需要创建CloudKit的权限验证。

container.setUpAuth().then(function(userInfo) {
  // Either a sign-in or a sign-out button will be added to the DOM here.
  if(userInfo) {
      self.gotoAuthenticatedState(userInfo);
  } else {
      self.gotoUnauthenticatedState();
  }
  self.fetchRecords(); // Records are public so we can fetch them regardless.
});

当验证解决后,它依据用户是否登录的状态,在Dom中添加一个登录或退出的按钮。你需要在页面中创建一个id为 “apple-sign-in-button”的div。这个container.setUpAuth 方法可以在登录以后,自动修改 div。
查询记录
下面是在 CloudKit 中查询 “ColudNote” 的代码。

self.fetchRecords = function() {
      var query = { recordType: 'CloudNote' };

  // Execute the query.
  return publicDB.performQuery(query).then(function (response) {
      if(response.hasErrors) {
          console.error(response.errors[0]);
          return;
      }
      var records = response.records;
      var numberOfRecords = records.length;
      if (numberOfRecords === 0) {
          console.error('No matching items');
          return;
      }

      self.notes(records);
  });
};

你可以看到我们根据recordtype建立了一个简单的查询,然后在公用数据库中进行了遍历查询。当然,你也可以在私有数据库中进行查询,只是在本例子中,我们只使用了公用数据库。

当我们查询到笔记的结果后,我们把它存储在slef.notes中,它受 knockout 检测。意味着会根据数据来重新渲染页面。这样查询到的笔记结果,就会显示在页面当中。

<div data-bind="foreach: notes">
<div class="panel panel-default">
    <div class="panel-body">
        <h4><span data-bind="text: fields.title.value"></span></h4>
        <p><span data-bind="text: fields.content.value"></span></p>
    </div>
</div>
</div>

模版会遍历所有的 notes 对象,打印 title 和 content 到 panel 上。

这里写图片描述
当用户登录后,在 panel 上看到 “Add New Note”。 所以 saveNewNote 方法需要实现把数据存储到 CloudKit 的功能。

if (self.newNoteTitle().length > 0 && self.newNoteContent().length > 0) {
self.saveButtonEnabled(false);

var record = {
    recordType: "CloudNote",
    fields: {
        title: {
            value: self.newNoteTitle()
        },
        content: {
            value: self.newNoteContent()
        }
    }
};

在方法的前半段,我们首先做基本的数据验证工作。并且依据表单的内容新建一个记录。

创建了新的记录后,我们把它存入 CloudKit。

publicDB.saveRecord(record).then(
function(response) {
    if (response.hasErrors) {
        console.error(response.errors[0]);
        self.saveButtonEnabled(true);
        return;
    }
    var createdRecord = response.records[0];
    self.notes.push(createdRecord);

    self.newNoteTitle("");
    self.newNoteContent("");
    self.saveButtonEnabled(true);
}
);

publicDB.saveRecord(record) 会把新创建的记录保存到公共数据库中,同时返回一个响应结果。然后记录会放入之前我们创建好的数组,无须再去查询一遍,同时表单会被清空,保存按钮也再次变为可点击状态。

iOS 应用
我们的示例是为了演示数据通过 CloudKit 如何在 iOS 和 web 应用之间共享,所以,还需要一个 iOS 客户端的应用。
这里写图片描述
创建这个应用,我们选择 Xcode 的默认模版 master detail application.

为了能够让 iCloud 工作,我们需要在设置中打开 capablities. 开启 iCloud 服务, Xcode 会自动的链接开发者中心,并为你准备好需要的权限项。

设置如下图。
这里写图片描述
现在已经准备好了,可以在应用中使用 CloudKit了。这里我们不详细介绍如何组织界面了,编写代码了,感兴趣的可以去看 comprehensive explanation of how to use CloudKit on iOS in iOS8-day-by-day。下面是应用的样子,标题限制在 viewController 的 title 上,内容显示在屏幕中央。
这里写图片描述
总结
希望本文能够让你对 CloudKit JS 的 API 有所了解,看它是如何的简单易用。我非常高兴苹果公司推出这样的基于 web 的 api。但我仍持部分保留意见,我想,我个人不会在项目中使用它的。

我很多第三方的云服务,比苹果的原生SDK,拥有更好的文档,更多的特性。而且,我始终无法在模拟器中使用 CloudKit,如果这个 bug 不及时解决,会对开发者带来很大困扰。

使用 CloudKit 的需求,是确定存在的,我推荐开发者都去尝试一下。想想,如果你的数据需要在另一个平台共享的场景。

延伸阅读
想要了解更多关于 CloudKit JS 和 web 服务的内容,请查看WWDC 2015的 session 710 CloudKit JS and Web Services. 另外,你可以在 Github 中,查看本文的示例代码。

猜你喜欢

转载自blog.csdn.net/weixin_40873814/article/details/78955976