Xamarin.iOS 中的 CloudKit

CloudKit 框架简化了访问 iCloud 的应用程序的开发。 这包括检索应用程序数据和资产权限,以及安全存储应用程序信息的能力。 此工具包允许用户使用 iCloud ID 访问应用程序而不共享个人信息,从而为用户提供匿名层。

开发人员可以专注于其客户端应用程序,让 iCloud 无需编写服务器端应用程序逻辑。 CloudKit 提供身份验证、专用和公共数据库以及结构化数据和资产存储服务。

重要

Apple 提供工具,用于帮助开发人员正确处理欧盟一般数据保护条例 (GDPR)。

要求

要完成本文所述的步骤,需要满足以下条件:

  • Xcode 7 和 iOS SDK – Apple 的 Xcode 和 iOS 8 API 需要在开发人员的计算机上安装和配置。
  • Visual Studio for Mac – 应在用户设备上安装和配置最新版本的 Visual Studio for Mac。
  • iOS 8 设备 – 运行最新版本的 iOS 8 设备进行测试。

什么是 CloudKit?

CloudKit 是一种向开发人员授予 iCloud 服务器访问权限的方法。 它为 iCloud 驱动器和 iCloud 照片图库提供基础。 macOS 和 iOS 设备上都支持 CloudKit。

How CloudKit is supported on both macOS and iOS devices

CloudKit 使用 iCloud 帐户基础结构。 如果设备上有用户登录到 iCloud 帐户,CloudKit 将使用其 ID 来标识该用户。 如果没有可用的帐户,将提供有限的只读访问权限。

CloudKit 支持公共数据库和专用数据库两个概念。 公共数据库提供用户有权访问的所有数据的“汤”。 专用数据库旨在存储绑定到特定用户的专用数据。

CloudKit 支持结构化数据和大容量数据。 它可以无缝处理大型文件传输。 CloudKit 负责在后台有效地将大型文件传输到 iCloud 服务器以及从 iCloud 服务器传输,从而让开发人员能够专注于其他任务。

注意

请务必注意,CloudKit 是一种传输技术。 它不提供任何持久性;它只允许应用程序有效地发送和接收来自服务器的信息。

到本文为止,Apple 最初免费为 CloudKit 提供具有上限的带宽和存储容量。 对于具有大型用户群的大型项目或应用程序,Apple 已暗示将提供负担得起的定价刻度。

在 Xamarin 应用程序中启用 CloudKit

在 Xamarin 应用程序可以利用 CloudKit 框架之前,必须按照使用功能使用权利指南中的详细说明正确预配应用程序。

若要访问 CloudKit,Entitlements.plist 文件必须包括“启用 iCloud”、“键值存储”和“CloudKit”权限。

示例应用

CloudKitAtlas 示例演示了如何将 CloudKit 与 Xamarin 配合使用。 以下步骤演示如何配置示例,它需要其他设置,不仅限于 CloudKit 本身需要的设置:

  1. 在 Visual Studio for Mac 或 Visual Studio 中打开项目。
  2. 解决方案资源管理器中,打开 Info.plist 文件,并确保捆绑包标识符与在预配设置过程中创建的应用 ID 中定义的捆绑包标识符匹配。
  3. 向下滚动到 Info.plist 文件的底部,然后选择“已启用的背景模式”、“位置更新”和“远程通知”。
  4. 右键单击解决方案中的 iOS 项目,然后选择“选项”。
  5. 选择“iOS 捆绑包签名”,然后选择上面创建的“开发人员标识”和“预配配置文件”。
  6. 确保 Entitlements.plist 包括“启用 iCloud”、“键值存储”和“CloudKit”。
  7. 确保应用程序存在 Ubiquity 容器。 示例: iCloud.com.your-company.CloudKitAtlas
  8. 保存对文件所做的更改。

有了这些设置,示例应用现在可以访问 CloudKit 框架 API 以及后台、位置和通知服务。

CloudKit API 概述

在 Xamarin iOS 应用程序中实现 CloudKit 之前,本文将介绍 CloudKit 框架的基础知识,其中包括以下主题:

  1. 容器 – iCloud 通信的独立孤岛。
  2. 数据库 – 公共和专用数据库可供应用程序使用。
  3. 记录 – 在此机制中,结构化数据移入和移出 CloudKit。
  4. 记录区域 – 是记录组。
  5. 记录标识符 – 已完全规范化,表示记录的特定位置。
  6. 引用 – 提供给定数据库中相关记录之间的父子关系。
  7. 资产 – 允许将大型非结构化数据的文件上传到 iCloud 并与给定的记录相关联。

容器

在 iOS 设备上运行的给定应用程序始终与该设备上的其他应用程序和服务一起运行。 在客户端设备上,应用程序会以某种方式孤立或沙盒化。 在某些情况下,这是字面上的沙盒,而在其他情况下,应用程序只是在它自己的内存空间中运行。

采用客户端应用程序并将其与其他客户端分离运行的概念非常强大,具有以下优势:

  1. 安全性 – 一个应用程序不能干扰其他客户端应用或 OS 本身。
  2. 稳定性 – 如果客户端应用程序崩溃,则无法停止 OS 中的其他应用。
  3. 隐私 – 每个客户端应用程序对设备中存储的个人信息的访问权限有限。

CloudKit 旨在提供上面所列优势,并将它们应用于使用基于云的信息:

CloudKit apps communicate using containers

就像在设备上运行的众多应用程序中的一个,应用程序与 iCloud 的通信也是众多通信中的一个。 其中每个不同的通信孤岛称为容器。

容器通过 CKContainer 类在 CloudKit 框架中公开。 默认情况下,一个应用程序与一个容器进行对话,此容器隔离该应用程序的数据。 这意味着,多个应用程序可以将信息存储到同一 iCloud 帐户,但此信息永远不会相互融合。

iCloud 数据的容器化还允许 CloudKit 封装用户信息。 这样,应用程序将对 iCloud 帐户和其中存储的用户信息具有有限的访问权限,同时仍然保护用户的隐私和安全性。

容器由应用程序开发人员通过 WWDR 门户完全管理。 容器的命名空间在所有 Apple 开发人员中是全局性的,因此该容器不仅必须对给定开发人员的应用程序是唯一的,而且对于所有 Apple 开发人员和应用程序来说都必须是唯一的。

Apple 建议在为应用程序容器创建命名空间时使用反向 DNS 表示法。 示例: iCloud.com.company-name.application-name

默认情况下,容器将一对一绑定到给定应用程序,但可以在应用程序之间共享。 因此,多个应用程序可以在单个容器上协调。 单个应用程序还可以与多个容器通信。

数据库

CloudKit 的主要功能之一是采用应用程序的数据模型并将该模型复制到 iCloud 服务器。 某些信息适用于创建它的用户,其他信息是为公开使用(如餐馆评论)而由用户创建的公共数据,也可能是开发人员已为应用程序发布的信息。 无论哪种情况,受众不仅仅是一个用户,而是一个用户社区。

CloudKit Container Diagram

容器内部最首要的就是公共数据库。 这是所有公共信息存在和融合的地方。 此外,应用程序的每个用户都有多个单独的专用数据库。

在 iOS 设备上运行时,应用程序只能访问当前登录的 iCloud 用户的信息。 因此,应用程序的容器视图如下所示:

The applications view of the container

它只能看到与当前登录的 iCloud 用户关联的公共数据库和专用数据库。

数据库通过 CKDatabase 类在 CloudKit 框架中公开。 每个应用程序都可以访问两个数据库:公共数据库和专用数据库。

容器是 CloudKit 的初始入口点。 以下代码可用于从应用程序的默认容器访问公共数据库和专用数据库:

using CloudKit;
//...

public CKDatabase PublicDatabase { get; set; }
public CKDatabase PrivateDatabase { get; set; }
//...

// Get the default public and private databases for
// the application
PublicDatabase = CKContainer.DefaultContainer.PublicCloudDatabase;
PrivateDatabase = CKContainer.DefaultContainer.PrivateCloudDatabase;

下面是数据库类型之间的差异:

公共数据库 专用数据库
数据类型 共享数据 当前用户的数据
配额 开发人员的配额中考虑 用户的配额中考虑
默认权限 全球可读 用户可读
编辑权限 通过记录类级别划分的 iCloud 仪表板角色 空值

记录

容器保留数据库,数据库内部是记录。 记录是一种机制,结构化数据在此机制中移入和移出 CloudKit:

Containers hold databases, and inside databases are records

记录通过包装键值对的 CKRecord 类在 CloudKit 框架中公开。 应用程序中对象的实例等效于 CloudKit 中的 CKRecord。 此外,每个 CKRecord 拥有一个记录类型,与对象的类等效。

记录具有实时架构,因此在移交进行处理之前,会向 CloudKit 描述数据。 自此,CloudKit 将解释信息并处理存储和检索记录的物流。

CKRecord 类还支持各种元数据。 例如,记录包含有关创建时间及创建该记录的用户的信息。 记录还包含有关上次修改时间及修改该记录的用户的信息。

记录包含更改标记的概念。 这是给定记录的修订版的一个早期版本。 更改标记用作确定客户端和服务器是否具有相同版本的给定记录的轻量级方法。

如上所述,CKRecords 会包装键值对,因此,以下类型的数据可以存储在记录中:

  1. NSString
  2. NSNumber
  3. NSData
  4. NSDate
  5. CLLocation
  6. CKReferences
  7. CKAssets

除了单值类型外,记录还可以包含上面列出的任何类型的同质数组。

以下代码可用于创建新记录并将其存储在数据库中:

using CloudKit;
//...

private const string ReferenceItemRecordName = "ReferenceItems";
//...

var newRecord = new CKRecord (ReferenceItemRecordName);
newRecord ["name"] = (NSString)nameTextField.Text;
await CloudManager.SaveAsync (newRecord);

记录区域

记录本身不存在于给定数据库中,记录组一起存在于记录区域中。 记录区域可以被视为传统关系数据库中的表:

Groups of records exist together inside a Record Zone

给定记录区域中可以有多个记录,给定数据库中可以有多个记录区域。 每个数据库都包含默认记录区域:

Every database contains a Default Record Zone and Custom Zone

这是记录的默认存储位置。 此外,还可以创建自定义记录区域。 记录区域表示原子提交和更改跟踪完成的基本粒度。

记录标识符

记录标识符表示为元组,其中包含客户端提供的记录名称和记录所在的区域。 记录标识符具有以下特征:

  • 它们由客户端应用程序创建。
  • 它们已完全规范化,表示记录的特定位置。
  • 通过将外部数据库中记录的唯一 ID 分配给记录名称,它们可用于桥接未存储在 CloudKit 中的本地数据库。

开发人员创建新记录时,可以选择传入记录标识符。 如果未指定记录标识符,将自动创建 UUID 并将其分配给记录。

开发人员创建新的记录标识符时,可以选择指定每个记录将属于的记录区域。 如果未指定,将使用默认记录区域。

记录标识符通过 CKRecordID 类在 CloudKit 框架中公开。 以下代码可用于创建新的记录标识符:

var recordID =  new CKRecordID("My Record");

参考

引用提供给定数据库中相关记录之间的关系。

References provide relationships between related Records within a given Database

在上面的示例中,父级拥有子级,因此子级是父记录的子记录。 关系从子记录到父记录,称为“反向引用”。

引用通过 CKReference 类在 CloudKit 框架中公开。 它们是让 iCloud 服务器了解记录之间的关系的一种方式。

引用提供级联删除背后的机制。 如果从数据库中删除父记录,则任何子记录(如关系中指定)也将从数据库中自动删除。

注意

使用 CloudKit 时,悬空指针是一种可能性。 例如,当应用程序提取了记录指针列表、选择了记录且随后申请了记录时,该记录可能不再存在于数据库中。 必须对应用程序进行编码才能正常处理这种情况。

虽然不是必需,但使用 CloudKit 框架时首选“反向引用”。 Apple 已经调整了系统,使之成为最有效的引用类型。

创建引用时,开发人员可以提供内存中已有的记录,也可以创建对记录标识符的引用。 如果使用记录标识符,并且数据库中不存在指定的引用,则会创建悬空指针。

下面是针对已知记录创建引用的示例:

var reference = new CKReference(newRecord, new CKReferenceAction());

资产

资产允许将大型非结构化数据的文件上传到 iCloud 并与给定的记录相关联:

Assets allow for a file of large, unstructured data to be uploaded to iCloud and associated with a given Record

在客户端上,将创建一个 CKRecord,描述要上传到 iCloud 服务器的文件。 将创建一个 CKAsset,以包含该文件并将其链接到描述该文件的记录。

将文件上传到服务器时,该记录将放置在数据库中,并将文件复制到特殊的批量存储数据库中。 将在记录指针和上传的文件之间创建链接。

资产通过 CKAsset 类在 CloudKit 框架中公开,用于存储大型非结构化数据。 由于开发人员从来不希望内存中有大型非结构化数据,因此资产是使用磁盘上的文件实现的。

资产由记录拥有,允许使用记录作为指针从 iCloud 检索资产。 这样,服务器就可以在删除拥有资产的记录时垃圾回收资产。

由于 CKAssets 意在处理大型数据文件,因此 Apple 设计了 CloudKit 以高效上传和下载资产。

以下代码可用于创建资产并将其与记录相关联:

var fileUrl = new NSUrl("LargeFile.mov");
var asset = new CKAsset(fileUrl);
newRecord ["name"] = asset;

我们现在介绍了 CloudKit 中的所有基本对象。 容器与应用程序关联且包含数据库。 数据库包含分组到记录区域且由记录标识符指向的记录。 将使用引用在记录之间定义父子关系。 最后,可以使用资产上传大型文件并将其关联到记录。

CloudKit 便利性 API

Apple 提供了两组不同的 API,用于使用 CloudKit:

  • 操作性 API – 提供 CloudKit 的每个功能。 对于更复杂的应用程序,此 API 提供对 CloudKit 的精细控制。
  • 便利性 API – 提供常用、预配置的一组 CloudKit 功能。 它提供了一个便利、简单的访问解决方案,用于在 iOS 应用程序中包括 CloudKit 功能。

便利性 API 通常是大多数 iOS 应用程序的最佳选择,Apple 建议从它开始。 本部分的剩余部分将介绍以下便利性 API 主题:

  • 保存记录。
  • 提取记录。
  • 更新记录。

常用设置代码

在开始使用 CloudKit 便利性 API 之前,需要一些标准设置代码。 首先修改应用程序的 AppDelegate.cs 文件,使其如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using Foundation;
using UIKit;
using CloudKit;

namespace CloudKitAtlas
{
    [Register ("AppDelegate")]
    public partial class AppDelegate : UIApplicationDelegate
    {
        public override UIWindow Window { get; set;}
        public CKDatabase PublicDatabase { get; set; }
        public CKDatabase PrivateDatabase { get; set; }

        public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
        {
            application.RegisterForRemoteNotifications ();

            // Get the default public and private databases for
            // the application
            PublicDatabase = CKContainer.DefaultContainer.PublicCloudDatabase;
            PrivateDatabase = CKContainer.DefaultContainer.PrivateCloudDatabase;

            return true;
        }

        public override void RegisteredForRemoteNotifications (UIApplication application, NSData deviceToken)
        {
            Console.WriteLine ("Registered for Push notifications with token: {0}", deviceToken);
        }

        public override void FailedToRegisterForRemoteNotifications (UIApplication application, NSError error)
        {
            Console.WriteLine ("Push subscription failed");
        }

        public override void ReceivedRemoteNotification (UIApplication application, NSDictionary userInfo)
        {
            Console.WriteLine ("Push received");
        }
    }
}

上面的代码将公共和专用 CloudKit 数据库作为快捷方式公开,使它们更易于在应用程序的其余部分使用。

接下来,将以下代码添加到将使用 CloudKit 的任何视图或视图容器:

using CloudKit;
//...

public AppDelegate ThisApp {
    get { return (AppDelegate)UIApplication.SharedApplication.Delegate; }
}

这会添加一个快捷方式,用于到达 AppDelegate 并访问上面创建的公共和专用数据库快捷方式。

有了此代码,让我们看看如何在 Xamarin iOS 8 应用程序中实现 CloudKit 便利性 API。

保存记录

在讨论记录时使用上述模式,以下代码将创建一个新记录,并使用便利性 API 将其保存到公共数据库:

private const string ReferenceItemRecordName = "ReferenceItems";
...

// Create a new record
var newRecord = new CKRecord (ReferenceItemRecordName);
newRecord ["name"] = (NSString)nameTextField.Text;

// Save it to the database
ThisApp.PublicDatabase.SaveRecord(newRecord, (record, err) => {
    // Was there an error?
    if (err != null) {
        ...
    }
});

关于上述代码需要注意的三件事:

  1. 通过调用 PublicDatabaseSaveRecord 方法,开发人员无需指定数据的发送方式、写入的区域等。便利性 API 本身正在处理所有这些细节。
  2. 调用是异步的,在调用完成时提供回调例程,无论成功还是失败。 如果调用失败,将提供错误消息。
  3. CloudKit 不提供本地存储/持久性;它只是一个传输介质。 因此,当发出保存记录的请求时,它会立即发送到 iCloud 服务器。

注意

由于移动网络通信具有“易丢”的性质,连接会不断停止或中断。因此开发人员在使用 CloudKit 时首先必须要考虑的一个注意事项是处理错误。

提取记录

创建记录并成功将其存储在 iCloud 服务器上后,使用以下代码检索记录:

// Create a record ID and fetch the record from the database
var recordID = new CKRecordID("MyRecordName");
ThisApp.PublicDatabase.FetchRecord(recordID, (record, err) => {
    // Was there an error?
    if (err != null) {
        ...
    }
});

与保存记录一样,上述代码是异步的,简单且要求很好地处理错误。

更新记录

从 iCloud 服务器提取记录后,可以使用以下代码修改记录并将更改保存回数据库:

// Create a record ID and fetch the record from the database
var recordID = new CKRecordID("MyRecordName");
ThisApp.PublicDatabase.FetchRecord(recordID, (record, err) => {
    // Was there an error?
    if (err != null) {

    } else {
        // Modify the record
        record["name"] = (NSString)"New Name";

        // Save changes to database
        ThisApp.PublicDatabase.SaveRecord(record, (r, e) => {
            // Was there an error?
            if (e != null) {
                 ...
            }
        });
    }
});

如果调用成功,PublicDatabaseFetchRecord 方法将返回 CKRecord。 然后,应用程序会修改记录并再次调用 SaveRecord 以将更改写回到数据库。

本部分演示了使用 CloudKit 便利性 API 时应用程序将使用的典型周期。 应用程序会将记录保存到 iCloud,从 iCloud 检索这些记录,修改记录并将这些更改保存回 iCloud。

为可伸缩性而设计

到目前为止,本文已介绍如何在每次处理应用程序时从 iCloud 服务器存储和检索该应用程序的整个对象模型。 虽然此方法适用于少量数据和非常小的用户群,但当信息和/或用户群增加时,此方法不会很好地缩放。

大数据,小设备

应用程序越受欢迎,数据库中的数据越多,在设备上缓存整个数据的可行程度就越低。 以下技术可用于解决此问题:

  • 在云中保留大型数据 – CloudKit 旨在高效处理大型数据。
  • 客户端应仅查看该数据的一小部分 – 降低在给定时间处理任何任务所需的最少数据。
  • 客户端视图可以更改 – 由于每个用户有不同的偏好,所显示数据部分可能会因用户而不同,并且任何给定数据部分的用户单个视图可能有所不同。
  • 客户端使用查询来聚焦视点 – 查询允许用户查看云中存在的较大型数据集的一小部分。

查询

如上所述,查询允许开发人员选择云中存在的较大型数据集的一小部分。 查询通过 CKQuery 类在 CloudKit 框架中公开。

查询结合了三种不同的内容:记录类型 (RecordType)、谓词 (NSPredicate) 和(可选)排序描述符 (NSSortDescriptors)。 CloudKit 支持大多数 NSPredicate

支持的谓词

使用查询时,CloudKit 支持以下类型的 NSPredicates

  1. 名称等于变量中存储的值的匹配记录:

    NSPredicate.FromFormat(string.Format("name = '{0}'", recordName))
    
  2. 允许匹配基于动态键值,以便不必在编译时知道该键:

    NSPredicate.FromFormat(string.Format("{0} = '{1}'", key, value))
    
  3. 记录的值大于给定值的匹配记录:

    NSPredicate.FromFormat(string.Format("start > {0}", (NSDate)date))
    
  4. 记录的位置在给定位置 100 米以内的匹配记录:

    var location = new CLLocation(37.783,-122.404);
    var predicate = NSPredicate.FromFormat(string.Format("distanceToLocation:fromLocation(Location,{0}) < 100", location));
    
  5. CloudKit 支持标记化搜索。 此调用将创建两个令牌,一个用于 after,另一个用于 session。 它将返回包含这两个令牌的记录:

    NSPredicate.FromFormat(string.Format("ALL tokenize({0}, 'Cdl') IN allTokens", "after session"))
    
  6. CloudKit 支持使用 AND 运算符联接的复合谓词。

    NSPredicate.FromFormat(string.Format("start > {0} AND name = '{1}'", (NSDate)date, recordName))
    

创建查询

以下代码可用于在 Xamarin iOS 8 应用程序中创建 CKQuery

var recordName = "MyRec";
var predicate = NSPredicate.FromFormat(string.Format("name = '{0}'", recordName));
var query = new CKQuery("CloudRecords", predicate);

首先,它会创建一个谓词,以仅选择与给定名称匹配的记录。 然后,它会创建一个查询,该查询将选择与谓词匹配的给定记录类型的记录。

执行查询

创建查询后,使用以下代码执行查询并处理返回的记录:

var recordName = "MyRec";
var predicate = NSPredicate.FromFormat(string.Format("name = {0}", recordName));
var query = new CKQuery("CloudRecords", predicate);

ThisApp.PublicDatabase.PerformQuery(query, CKRecordZone.DefaultRecordZone().ZoneId, (NSArray results, NSError err) => {
    // Was there an error?
    if (err != null) {
       ...
    } else {
        // Process the returned records
        for(nint i = 0; i < results.Count; ++i) {
            var record = (CKRecord)results[i];
        }
    }
});

上述代码采用上面创建的查询,并针对公共数据库执行该查询。 由于未指定任何记录区域,因此将搜索所有区域。 如果未发生错误,将返回与查询参数匹配的 CKRecords 数组。

查询的一种思路是,它们是轮询,非常适合通过大型数据集进行分割处理。 但是,查询不适合大型静态数据集,原因如下:

  • 对设备电池使用寿命产生不利影响。
  • 对网络流量不利。
  • 对用户体验不利,因为他们看到的信息受应用程序轮询数据库的频率限制。 当今的用户在发生更改时需要推送通知。

订阅

处理大型静态数据集时,不应在客户端设备上执行查询,它应代表客户端在服务器上运行。 查询应在后台运行,并且应在每个记录保存后执行,无论是由当前设备还是触摸同一数据库的其他设备执行。

最后,在运行服务器端查询时,应将推送通知发送到附加到数据库的每个设备。

订阅通过 CKSubscription 类在 CloudKit 框架中公开。 它们结合了记录类型 (RecordType)、谓词 (NSPredicate) 和 Apple 推送通知 (Push)。

注意

CloudKit 推送稍有增强,因为它们包含一个其中含有 CloudKit 特定信息的有效负载,例如导致推送发生的原因。

订阅的工作原理

在 C# 代码中实现订阅之前,让我们快速了解订阅的工作原理:

An overview of how subscriptions work

上图显示了典型的订阅过程,如下所示:

  1. 客户端设备会创建一个新的订阅,其中包含将触发订阅的一组条件,以及发生触发时将发送的推送通知。
  2. 订阅将发送到将被添加到现有订阅集合中的数据库。
  3. 另一台设备会创建新的记录并将该记录保存到数据库。
  4. 数据库会搜索其订阅列表,查看新记录是否与其任何条件匹配。
  5. 如果找到匹配项,会将推送通知发送到注册订阅的设备,其中包含导致订阅触发的记录的相关信息。

知道这一点后,让我们看看如何在 Xamarin iOS 8 应用程序中创建订阅。

创建订阅

以下代码可用于创建订阅:

// Create a new subscription
DateTime date;
var predicate = NSPredicate.FromFormat(string.Format("start > {0}", (NSDate)date));
var subscription = new CKSubscription("RecordType", predicate, CKSubscriptionOptions.FiresOnRecordCreation);

// Describe the type of notification
var notificationInfo = new CKNotificationInfo();
notificationInfo.AlertLocalizationKey = "LOCAL_NOTIFICATION_KEY";
notificationInfo.SoundName = "ping.aiff";
notificationInfo.ShouldBadge = true;

// Attach the notification info to the subscription
subscription.NotificationInfo = notificationInfo;

首先,它会创建一个谓词,该谓词提供触发订阅的条件。 接下来,它会根据特定的记录类型创建订阅,并设置触发器测试时间的选项。 最后,它会定义触发订阅时将发生的通知类型并将其附加到订阅。

保存订阅

创建订阅后,以下代码会将其保存到数据库:

// Save the subscription to the database
ThisApp.PublicDatabase.SaveSubscription(subscription, (s, err) => {
    // Was there an error?
    if (err != null) {

    }
});

使用便利性 API 时,调用是异步的、简单的,并提供简单的错误处理。

处理推送通知

如果开发人员以前使用过 Apple 推送通知 (APS),则应该熟悉 CloudKit 生成的通知的处理过程。

AppDelegate.cs 中,替代 ReceivedRemoteNotification 类,如下所示:

public override void ReceivedRemoteNotification (UIApplication application, NSDictionary userInfo)
{
    // Parse the notification into a CloudKit Notification
    var notification = CKNotification.FromRemoteNotificationDictionary (userInfo);

    // Get the body of the message
    var alertBody = notification.AlertBody;

    // Was this a query?
    if (notification.NotificationType == CKNotificationType.Query) {
        // Yes, convert to a query notification and get the record ID
        var query = notification as CKQueryNotification;
        var recordID = query.RecordId;
    }
}

上述代码要求 CloudKit 将 userInfo 分析为 CloudKit 通知。 接下来,将提取有关警报的信息。 最后,测试通知的类型,并相应地处理通知。

本部分介绍了如何使用查询和订阅回答上述“大数据,小设备”问题。 应用程序会将其大型数据保留在云中,并使用这些技术来提供此数据集的视图。

CloudKit 用户帐户

如本文开头所述,CloudKit 基于现有的 iCloud 基础结构构建。 以下部分将详细介绍如何使用 CloudKit API 向开发人员公开帐户。

身份验证

处理用户帐户时,第一个考虑因素是身份验证。 CloudKit 支持通过设备上当前登录的 iCloud 用户进行身份验证。 身份验证在后台进行,由 iOS 处理。 这样,开发人员就永远不必担心实现身份验证的详细信息。 他们测试只是为了查看用户是否已登录。

用户帐户信息

CloudKit 向开发人员提供以下用户信息:

  • 标识 – 唯一标识用户的方法。
  • 元数据 – 能够保存和检索有关用户的信息。
  • 隐私 – 所有信息都在具有隐私意识的区域处理。 除非用户同意,否则不会公开任何内容。
  • 发现 – 让用户能够发现正在使用同一应用程序的朋友。

接下来,我们将详细查看这些主题。

标识

如上所述,CloudKit 为应用程序提供了一种唯一标识给定用户的方法:

Uniquely identifing a given user

有一个客户端应用程序在用户的设备以及 CloudKit 容器中的所有特定用户专用数据库上运行。 客户端应用程序将链接到其中一个特定用户。 这基于在设备上本地登录到 iCloud 的用户。

由于来自 iCloud,因此用户信息有丰富的后备存储。 由于 iCloud 实际上是托管容器,因此它可以关联用户。 在上面的图形中,iCloud 帐户 user@icloud.com 的用户链接到当前客户端。

对于每个容器,将创建一个随机生成的唯一用户 ID,并与用户的 iCloud 帐户(电子邮件地址)相关联。 此用户 ID 将返回到应用程序,并可以采用开发人员认为合适的任何方式使用。

注意

同一 iCloud 用户在同一设备上运行的不同应用程序将具有不同的用户 ID,因为它们连接到不同的 CloudKit 容器。

以下代码获取设备上当前登录的 iCloud 用户的 CloudKit 用户 ID:

public CKRecordID UserID { get; set; }
...

// Get the CloudKit User ID
CKContainer.DefaultContainer.FetchUserRecordId ((recordID, err) => {
    // Was there an error?
    if (err!=null) {
        Console.WriteLine("Error: {0}", err.LocalizedDescription);
    } else {
        // Save user ID
        UserID = recordID;
    }
});

上述代码要求 CloudKit 容器提供当前登录用户的 ID。 由于此信息来自 iCloud 服务器,因此调用是异步的,需要处理错误。

元数据

CloudKit 中的每个用户都有描述它们的特定元数据。 此元数据表示为 CloudKit 记录:

Each user in CloudKit has specific Metadata that describes them

在专用数据库中查找容器的特定用户,有一个记录定义了该用户。 公共数据库中有许多用户记录,容器的每个用户都有一个。 其中一个用户记录的记录 ID 与当前登录用户的记录 ID 匹配。

公共数据库中的用户记录是全局可读的。 在大多数情况下,它们被视为普通记录,并有 CKRecordTypeUserRecord 类型。 这些记录由系统保留,不适用于查询。

使用以下代码访问用户记录:

public CKRecord UserRecord { get; set; }
...

// Get the user's record
PublicDatabase.FetchRecord(UserID, (record ,er) => {
    //was there an error?
    if (er != null) {
        Console.WriteLine("Error: {0}", er.LocalizedDescription);
    } else {
        // Save the user record
        UserRecord = record;
    }
});

上述代码要求公共数据库返回我们访问的上述用户 ID 的用户记录。 由于此信息来自 iCloud 服务器,因此调用是异步的,需要处理错误。

隐私

默认情况下,CloudKit 的旨在保护当前登录用户的隐私。 默认情况下不会公开有关用户的个人标识信息。 在某些情况下,应用程序需要有关用户的有限信息。

在这些情况下,应用程序可以请求用户披露此信息。 将向用户显示一个对话框,要求他们同意公开其帐户信息。

发现

假设用户同意应用程序有限制地访问其用户帐户信息,则该应用程序的其他用户可以发现它们:

A user can be discoverable to other users of the application

客户端应用程序正在与容器通信,容器正在与 iCloud 通信以访问用户信息。 用户可以提供电子邮件地址,可以通过发现来获取有关用户的信息。 (可选)用户 ID 还可用于发现有关用户的信息。

CloudKit 还提供了一种方法,通过查询整个通讯簿来发现可能是当前登录 iCloud 用户的朋友的任何用户的信息。 CloudKit 进程将拉取用户的通讯录,并使用电子邮件地址查看它是否可以找到与这些地址匹配的应用程序的其他用户通讯录。

这样,应用程序就可以利用用户的通讯录,而无需提供对联系人的访问权限或要求用户批准访问联系人。 应用程序没有可用的联系信息,只有 CloudKit 进程有权访问。

若要回顾,有三种不同类型的输入可用于用户发现:

  • 用户记录 ID – 可以针对当前登录 CloudKit 用户的用户 ID 执行发现。
  • 用户电子邮件地址 – 用户可以提供电子邮件地址,它可用于发现。
  • 通讯录 – 用户的地址簿可用于发现电子邮件地址与联系人中列出的电子邮件地址相同的应用程序用户。

用户发现将返回以下信息:

  • 用户记录 ID – 公共数据库中用户的唯一 ID。
  • 名字和姓氏 – 存储在公共数据库中。

仅针对已同意使用发现的用户返回此信息。

以下代码将发现设备上当前登录到 iCloud 的用户的相关信息:

public CKDiscoveredUserInfo UserInfo { get; set; }
//...

// Get the user's metadata
CKContainer.DefaultContainer.DiscoverUserInfo(UserID, (info, e) => {
    // Was there an error?
    if (e != null) {
        Console.WriteLine("Error: {0}", e.LocalizedDescription);
    } else {
        // Save the user info
        UserInfo = info;
    }
});

使用以下代码查询通讯录中的所有用户:

// Ask CloudKit for all of the user's friends information
CKContainer.DefaultContainer.DiscoverAllContactUserInfos((info, er) => {
    // Was there an error
    if (er != null) {
        Console.WriteLine("Error: {0}", er.LocalizedDescription);
    } else {
        // Process all returned records
        for(int i = 0; i < info.Count(); ++i) {
            // Grab a user
            var userInfo = info[i];
        }
    }
});

在本部分中,我们介绍了 CloudKit 可以提供给应用程序的用户帐户的四个主要访问权限领域。 从获取用户的标识和元数据,到在 CloudKit 中构建隐私策略,最后到发现应用程序的其他用户。

开发和生产环境

CloudKit 为应用程序的记录类型和数据提供单独的开发和生产环境。 开发环境是一个更灵活的环境,仅适用于开发团队的成员。 当应用程序向记录添加新字段并在开发环境中保存该记录时,服务器会自动更新架构信息。

开发人员可以使用此功能在开发期间更改架构,从而节省时间。 一个注意事项是,将字段添加到记录后,无法以编程方式更改与该字段关联的数据类型。 若要更改字段的类型,开发人员必须在 CloudKit 仪表板中删除该字段,并使用新类型再次添加该字段。

在部署应用程序之前,开发人员可以使用 CloudKit 仪表板将其架构和数据迁移到生产环境。 针对生产环境运行时,服务器会阻止应用程序以编程方式更改架构。 开发人员仍可使用 CloudKit 仪表板进行更改,但尝试在生产环境中将字段添加到记录会导致错误。

注意

iOS 模拟器仅适用于开发环境。 当开发人员准备好在生产环境中测试应用程序时,需要一台实体 iOS 设备。

交付已启用 CloudKit 的应用

在交付使用 CloudKit 的应用程序之前,需要将其配置为面向生产 CloudKit 环境,否则 Apple 将拒绝该应用程序。

请执行以下操作:

  1. 在 Visual Studio for Mac 中,编译适用于“发布”>“iOS 设备”的应用程序:

    Compile the application for Release

  2. 从“生成”菜单中选定“存档”:

    Select Archive

  3. 存档”将在 Visual Studio for Mac 中创建并显示:

    The Archive will be created and displayed

  4. 启动 Xcode

  5. 在“窗口”菜单中,选择“组织者”:

    Select Organizer

  6. 选择应用程序的存档,然后单击“导出...”按钮:

    The application's archive

  7. 选择导出方法,然后单击“下一步”按钮:

    Select a method for export

  8. 从下拉列表中选择“开发团队”,然后单击“选择”按钮:

    Select the Development Team from the dropdown list

  9. 从下拉列表中选择“生产”,然后单击“下一步”按钮:

    Select Production from the dropdown list

  10. 查看设置,然后单击“导出”按钮:

    Review the setting

  11. 选择一个位置以生成产生的应用程序 .ipa 文件。

此过程与将应用程序直接提交到 iTunes Connect 类似,只需单击“提交...”按钮,而不是在“组织者”窗口中选择“存档”后单击“导出...”。

何时使用 CloudKit

如本文所述,CloudKit 为应用程序提供了一种简单的方法来存储和检索 iCloud 服务器中的信息。 也就是说,CloudKit 不会淘汰或弃用任何现有工具或框架。

用例

以下用例应该能够帮助开发人员确定何时使用特定的 iCloud 框架或技术:

  • iCloud 键值存储 – 异步保持少量数据最新,非常适合处理应用程序首选项。 但是,对于极少量的信息,它受到限制。
  • iCloud 驱动器 – 基于现有的 iCloud 文档 API 而构建,提供一个简单的 API,用于从文件系统同步非结构化数据。 它在 Mac OS X 上提供完全脱机缓存,非常适合以文档为中心的应用程序。
  • iCloud 核心数据 – 允许在所有用户设备之间复制数据。 数据是单用户,非常适合保持结构化的专用数据同步。
  • CloudKit – 提供结构化和大容量的公共数据,并且能够处理大型数据集和大型非结构化文件。 它与用户的 iCloud 帐户绑定,并提供客户端定向数据传输。

请记住这些用例,开发人员应选择正确的 iCloud 技术,以便提供当前所需的应用程序功能并为未来的增长提供良好的可伸缩性。

总结

本文涵盖 CloudKit API 的快速介绍。 它演示了如何预配和配置 Xamarin iOS 应用程序以使用 CloudKit。 它涵盖了 CloudKit 便利性 API 的功能。 它演示了如何使用查询和订阅来设计已启用 CloudKit 的应用程序以实现可伸缩性。 最后,它显示了 CloudKit 向应用程序公开的用户帐户信息。