官方地址:iOSAppProgramming Guide -> iCloud Storage
iCloud支持两种应用存储:
iCloudApp的设计考虑
首先需要确认的是采用document storage还是key-value datastorage。documentstorage用于存储应用数据,要么是应用创建并私有管理的数据,要么是用户创建的数据。所有用户面对的数据都应该是documentstorage,例如用户创建的文档。key-value datastorage主要用于非关键的配置数据,你希望在多个app实例中共享,例如应用使用的参数和配置信息(典型的例子如Newsstand应用中用户上一次阅读的刊物和阅读位置)。
document storage和 key-value data storage的区别:
属性 | Document Storage | Key-Value storage |
管理的数据类型? | 文件和目录 | 只能是Property-list数据 |
什么时候应该使用? | 使用Document Storage来管理应用的关键数据。与应用main datamodel直接相关的文件和数据,总是应该使用DocumentStorage。如用户文档、私有的app数据文件、以及应用或用户生成的数据文件 | 参数或其它配置数据,如果需要在多个app实例之间共享,并且不是关键数据,就可以使用key-vlauestorage。只能存储property list数据,并且容量有限。 |
是否需要file presenter和file coordinator | 是 | 否 |
怎样定位数据? | 使用 NSMetadataQuery 对象来查找文件 | 使用默认的 NSUbiquitousKeyValueStore对象来获取已知key对应的值 |
如何管理数据? | 使用NSFileManager类来管理文件和目录。使用标准文件系统函数来打开、关闭、读取和写入文件 | 使用默认的NSUbiquitousKeyValueStore 对象来获取和设置key和value |
能存储多少数据? | 只受用户iCloud账户的空间限制 | 限制为64KB(单个key也限制为64KB) |
怎样处理冲突? | 应用的 file presenters 必须手工解决冲突 | 最后设置的值总是当前值。设备提供的时间戳用于确定最新的值 |
要求设置哪个entitlement? | com.apple.developer.ubiquity-container-identifiers | com.apple.developer.ubiquity-kvstore-identifier |
数据什么时候同步? | 设备端发生变化时,iCloud总是会拉取文件元数据和数据;设备总是会拉取文件元数据,但是直到应用试图使用文件时才会拉取文件数据 | 定期在设备和iCloud之间传输key-value数据 |
怎样在运行时检查iCloud可用? | 对某个你已经注册的容器目录调用 URLForUbiquityContainerIdentifier:方法,如果方法返回nil,表示document storage不可用 | 调用 NSUbiquitousKeyValueStore 的 synchronize方法,如果返回YES,iCloud可用,并且有修改同步到本地用户默认数据库;如果返回NO,iCloud要么不可用,要么没有变化需要同步。无论如何,都可以直接使用本地的用户默认数据库。 |
系统提供了什么用户界面支持? | 没有,应用负责提供支持iCloud的信息和界面。 | 没有,多数情况下,你不应该让用户知道key-value数据存储在本地还是iCloud中。 |
另一个设计考虑是应用的用户界面如何整合iCloud支持。特别是文档,有时候你需要提醒用户文档的状态,比如是否已经下载、版本冲突需要解决等。一般都需要在界面上增加一些不引人注目的元素,以在适当的时候提示用户。
配置应用的iCloudEntitlements
使用iCloud的应用必须以iCloud特定的Entitlements签名。这些entitlements为应用提供一层安全性,确保只有你的应用才能访问自己创建的文档。系统也依赖于你提供的Entitlement值来区分用户iCloud账户中你的应用与其它应用的文档。
Xcode中启用iCloudEntitlements:
当你启用Apptarget的Entitlements时,Xcode自动为你的应用配置了document storage和key-value datastorage。每个Entitlement包含一个key,是一个或多个容器标识字符串。容器标识字符串标识了iCloud中的一个容器目录,你用来存储应用的文件。
Xcode按以下方式来配置Entitlements:
你在Xcode中设置的bundleID并不是完整限定的容器标识字符串。完整限定的容器标识的格式是: <TEAM_ID>.<BUNDLE_IDENTIFIER>,其中<TEAM_ID>是10个字符的团队标识;<BUNDLE_IDENTIFIER>则是iCloud容器域中的某个bundle ID。当在代码中获取某个容器目录的URL时,你需要传递完整限定的字符串给 URLForUbiquityContainerIdentifier:方法。不过你也可以传递nil,来获取列表中的第一个容器目录的URL
通过在Entitlements中指定多个容器标识,使用iCloud documentstorage的应用可以读取和写入内容到多个容器目录。iCloud容器域允许你指定多个字符串。第一个字符串是应用的main容器标识,任何额外的字符串则代表你其它应用的容器标识。查找时会返回所有可用容器目录中的所有文件。
使用iCloud DocumentStorage
iCloud DocumentStorage让你将文件和目录移动到用户的iCloud账户,并在那里管理这些文件。在某个设备上修改文件或目录,会先存储在本地,然后再通过本地daemon进程push到iCloud。文件传输对应用是透明的,因此应用只需直接操作文件。
设计应用使用iCloud DocumentStorage,需要一些重大的应用修改,主要包括:
App支持iCloud的大部分工作都在数据层。与iCloud的交互也主要是应用要存储使用的文件和目录。不过底层数据变化时,用户界面也需要一定的修改,以通知用户相关的状态。当然,应用应该尽可能地让用户无需关心本地还是iCloud存储。
检查iCloud DocumentStorage是否可用
每个AppleID用户都有一个免费的iCloud账户,但是用户可能禁用某个设备的iCloud。因此使用iCloud接口之前,必须先调用 URLForUbiquityContainerIdentifier:方法检确定iCloud是否可用。返回nil表示不可用,返回URL表示可用
第一次对指定的容器目录调用 URLForUbiquityContainerIdentifier:方法,iOS会扩展应用Sandbox来包含该容器目录。因此需要至少成功调用一次该方法,以确保iCloud可用,main容器目录也可以访问。
整合 File Presenters到应用
iCloud中存储的所有文件和目录都由filepresenter对象管理;你对这些文件和目录的修改都必须通过一个filecoordinator对象。
filepresenter实现了 NSFilePresenter协议,主要的职责是作为特定文件或目录的响应代理。在外部源能够修改一个文件之前,注册的filepresenter会得到通知,并有机会执行任何必要的簿记(bookkeeping)工作。
当应用想要修改一个文件时,它必须通过一个 NSFileCoordinator对象来执行修改,本质上会锁住文件。file coordinator 阻止外部源修改文件,并且递送相关的通知给其它 filepresenters。
整合 filepresenter 到应用最简单的方法是使用 UIDocument 类。这个类实现了 NSFilePresenter协议,并为你处理了所有文件相关的管理。应用唯一需要做的事情,就是在得到通知时,读取和写入文档数据。不管是用户生成内容的文件(因此直接显示给用户),还是应用替用户创建的文件(无需用户交互),都可以使用UIDocument。
操作iCloud中的文件和目录
iCloud文件和目录和本地文件目录一样,应用使用相同的技术来管理文件和目录。你可以打开、创建、移动、复制、读取、写入、删除、以及其它操作。本地文件目录和iCloud文件目录的唯一区别是访问的URL不同。本地文件目录的URL相对于应用Sandbox,而iCloud文件目录的URL则相对于相应的iCloud容器目录。
要移动一个文件或目录到iCloud:
移动一个文件或目录到iCloud时,系统从应用Sandbox中复制该item到private本地存储,这样才能被iClouddaemon监测到。尽管文件此时已经不在应用Sandbox中,应用仍然对其拥有完全访问。虽然文件仍然有一份拷贝保留在当前设备的本地,文件同时还发送到了iCloud,这样就能分布到其它设备去。iClouddaemon处理了所有工作,确保本地拷贝与iCloud是相同的。因此从应用的角度来说,文件就是在iCloud中。
你对iCloud中的文件和目录的所有修改都需要使用一个 filecoordinator 对象。包括移动、删除、复制、重命名等操作。file coordinator确保iClouddaemon不会同时修改文件或目录,并确保你修改时能够及时地通知其它人。
命名文件和目录时,尽量使用字母数字,避免使用特殊标点和其它特殊字符。同时你还应该假设文件名是大小写不敏感的(因为iCloud支持Windows)。最后尽量保持文件名简单,以确保这些文件能够在不同文件系统中正确地处理。
选择一种响应版本冲突的策略
iCloud文件的版本冲突不可避免,因此应用必须对冲突采取一种解决策略。当应用的两个实例同时修改一个文件并传输给iCloud时就会发生冲突。iCloud会保存所有冲突的版本,并通知应用的filepresenter发生了冲突需要解决。
App应该尽快地尝试解决版本冲突,当冲突发生时,其中一个文件被指定为currentfile,其它所有版本都是冲突版本。当前文件和冲突版本文件都由 NSFileVersion对象来管理,可以通过类方法来获取。
解决冲突:
更新当前文件:
设置所有冲突版本对象的 resolved属性为YES。设置这个属性会导致冲突版本对象(及相应的文件)从用户的iCloudstorage中删除。如果使用UIDocument类,你通过 observing文档状态变化通知,并检查 documentState属性来判断是否发生冲突
如果使用自定义 filepresenter,每当报告有一个新版本时,你都应该检查它是否冲突版本。
整合查找(Search)到应用基础架构
和应用Sandbox不同,iCloud中的文件添加和删除,你的应用可能都不会直接知道。在这台设备上创建的文件,最终会出现在另一台设备中。如果应用不主动查找文件,那就不能及时地出现在用户界面中。因此应用需要使用NSMetadataQuery 对象来查找iCloud容器目录中的文件或目录。
当应用在前台时,你可以保留一个 metadata query一直运行,来接收文件添加或删除的通知;应用进入后台时,就停止这个 metadata query查询。
只有 iCloud启用,并且相应的容器目录已经创建时,metadata查询才会有返回结果。在运行时,确保使用 URLForUbiquityContainerIdentifier:方法来检测iCloud已经启用,而且应用支持的容器目录也可用。这个方法在容器目录不存在时,会自动创建出来。
Metadata查找应用entitlement中设置的所有容器目录,并返回合并后的所有结果。如果你希望只查找一个容器目录,可以使用 URLForUbiquityContainerIdentifier:方法来获得该容器目录的URL,然后使用 NSFileManager类来获取目录内容的静态列表。
确定文件或目录的传输状态
你写到iCloud容器目录的item会尽可能快地自动传输到iCloud服务器。但是由于网络和设备类型的原因,文件或目录可能不会立即下载到设备或上传到服务器。如果你需要确定文件的状态,可以使用NSURL 的 getResourceValue:forKey:error: 方法,来获取以下属性的值:
虽然iCloud服务器会非常努力地拉取应用在本地做的修改,但是iOS设备通常却不会主动从服务器拉取修改,除非你试图访问该文件。如果你试图打开一个正在下载的文件,iOS会阻塞发起打开请求的线程,直到文件被完全下载并且可用。因此如果你担心潜在的延迟,就根据需要检查文件的当前状态,同时更新用户界面,提示用户当前文件正在下载,暂时不可用。
使用尚未下载完成的文件
iCloud中的文件发生改变时,iOS设备不会自动下载这些修改数据。相反iOS设备会下载文件的元数据,因此知道此时文件已经有修改。实际修改的数据只有以下情况发生时,才会被下载:
如果应用打开尚未下载完成的文件,用于打开文件的 file coordinator会阻塞你的应用,直到文件或修改被完全下载。如果文件或修改比较大,可能会导致很差的用户体验,因此试图打开一个文件之前,应该首先检查文件的下载状态。NSURL类定义了iCloud item相关的属性,可以检查iCloud文件的状态
检查文件是否已经下载完成:
- (BOOL)downloadFileIfNotAvailable:(NSURL*)file {
NSNumber* isIniCloud = nil;
if ([filegetResourceValue:&isIniCloudforKey:NSURLIsUbiquitousItemKey error:nil]) {
// If theitem is in iCloud, see if it is downloaded.
if([isIniCloud boolValue]) {
NSNumber* isDownloaded = nil;
if ([filegetResourceValue:&isDownloadedforKey:NSURLUbiquitousItemIsDownloadedKey error:nil]) {
if([isDownloaded boolValue])
return YES;
//Download the file.
NSFileManager* fm = [NSFileManagerdefaultManager];
[fmstartDownloadingUbiquitousItemAtURL:file error:nil];
returnNO;
}
}
}
// Return YES as long as anexplicit download was not started.
return YES;
}
为iCloud调整用户界面
所有为了iCloud所做的用户界面调整,都应该尽量不引人注意。当iCloud不可用时,你存储在iCloud中的文档和你存储在本地是完全一样的。唯一的区别是文件系统中的位置。因此大部分用户界面都应该保持一致。
以下情况可能需要针对iCloud修改用户界面:
iCloud结合Database使用
只有应用使用CoreData来管理数据库时,才能将SQLite数据库整合到iCloud。不支持使用SQLite接口访问iCloud中的live数据库,这样做很可能会毁坏你的数据库。
但是只要你遵守一些额外的步骤,来设置Core Data结构,你就可以创建基于SQLite的CoreData,从而启用iCloud支持。当然,其它类型的Core DataStore(不基于SQLite),则无需修改可以直接支持iCloud。
使用Core Data和SQLite store时,实际的数据库文件不会传输到iCloud服务器。相反,每个设备都维护自己的SQLitestore拷贝,并通过将修改写入到日志文件来同步它的内容。设备与iCloud之间真正传输的是log file,在每个设备中,CoreData拿到logfile的内容,然后使用这些内容来更新本地数据库。当然最终的效果就是每个本地数据库都拥有完全相同的修改。
设置Core Data store来处理iCloud,只需要你执行很少的额外工作。但是具体的步骤取决于你使用CoreData store作为中央库(Central Library),还是为单个文档分别创建独立的Core DataStore。
使用Core Data管理文档
应 用管理Core Data store为单独的文档时,可使用 UIManagedDocument对象来管理单个文档。UIManagedDocument 类自动在应用bundle中查找所有managed objectmodel,并使用它们作为文档数据的基础(你也可以覆盖 managedObjectModel 属性自定义指定子类的objectmodels)。由于大部分数据由managed object context处理,意味着你通常可以直接使用UIManagedDocument类而不需要继承子类。UIDocumemnt的自动保存行为,能够自动处理所有文档的保存工作。
创建新文档时,执行以下步骤:
当 你创建一个新文档时,Core Data创建一个filepackage,包含文档的内容。这些内容有一个 DocumentMetadata.plist文件,以及一个包含SQLite data store的目录。除了SQLite data store(保留在本地),filepackage中的任何东西都会被传输到iCloud服务器。
打开iCloud中的现有文档时,执行以下步骤:
应 用第一次打开其它设备创建的Core Data文档时,Core Data会自动检测到SQLitestore不存在,并在本地创建一个。然后使用 NSPersistentStoreUbiquitousContentNameKey键的值(你添加到属性的那个)来获取适当的transactionlogs,并重新构建数据库的内容。从这时候开始,你就可以修改该文档并保存到iCloud。你所做的修改会保存到新的logfile,这样其它设备就能将其整合到它们的SQLite store。
当应用从iCloud中 接收到文档修改时,CoreData会自动将这些修改合并到文档的SQLitestore,并给应用发送 NSPersistentStoreDidImportUbiquitousContentChangesNotification通知。应用必须注册这个通知,并使用它来刷新任何修改的记录。如果应用不刷新本地的数据拷贝,就会将老的修改重新写到iCloud,并产生一个冲突版本。通过及时地整合其它设备的修改,应用就能避免类似的冲突。
删 除一个文档时,你必须同时删除文档的file package,以及包含文档transactionlogs的目录。同时删除所有这些东西,要求你使用 NSFileCoordinator对象来执行coordinated写入操作。文档的DocumentMetadata.plist文件包含一个 NSPersistentStoreUbiquitousContentURLKey键,它的值就是文档transaction logs目录的URL。
使用Core Data管理中央库(Central Library)
如果应用使 用Central Core Data store来管理数据,就应该将datastore直接存放在应用Sandbox目录。使用一个Central data store的应用,通常只有一个persistentstore coordinator对象和一个persistent store对象。因此,最简单的解决办法是将Core Datastore保留在应用Sandbox,仅使用iCloud来同步修改。
在本地创建SQLite store时,执行以下步骤:
因为你只有一个datastore,你可以为 NSPersistentStoreUbiquitousContentNameKey键指定任何名字。对于 NSPersistentStoreUbiquitousContentURLKey键,应该指定为应用某个iCloud容 器目录中的某个目录URL。换句话说,这个URL应该基于URLForUbiquityContainerIdentifier: 方法返回的URL构造而成。CoreData将修改写入到你指定的这个目录,其它设备则在这个目录中查找修改。当检测到修改时,CoreData会自动整合修改到本地的SQLite store,并通知你的应用。
同样,你也必须及时地响应iCloud相关的修改通知。这些通知使应用能确保使用最新的数据,如果你使用老版本的数据,就可能覆盖其它设备的新数据,并产生冲突版本。
为Core Data Transaction Logs指定自定义位置
应用Core Data Store的修改使用transaction logs来保存,这些logs存储在用户iCloud账户的一个特殊目录中。应用的所有Core DataStores全部使用同一个目录来保存transaction logs。默认情况下,这个目录的名字和应用bundleID相同,并保存在应用默认iCloud容器目录中。如果你已经使用这个目录作为其它用途,你可以通过修改CoreData stores的选项来修改目录的名字。
在 基于文档的应用中,要自定义transaction logs目录,必须修改每个 UIManagedDocument对象的 persistentStoreOptions属性Dictionary。在字典中增加 NSPersistentStoreUbiquitousContentURLKey键,并设置值为你希望使用的自定义目录URL。这个URL需要首先使用 URLForUbiquityContainerIdentifier:方法,并通过添加额外的路径来扩展URL。
如果应用使用单一的Core Data store管理所有数据,在创建 persistentstore时,调用 addPersistentStoreWithType:configuration:URL:options:error:方法,并在options参数中增加 NSPersistentStoreUbiquitousContentURLKey键,值也是你希望设置的目录URL。
使用iCloudKey-Value Data Storage
应用使用iCloud Key-Value datastorage来存储参数、或小量的非关键配置数据。key-value data storage概念上类似于本地userdefaults数据库,应用通常用来存储配置。区别在于iCloud数据在用户所有设备中你的所有应用实例间共享。
使 用 NSUbiquitousKeyValueStore类需要设置"com.apple.developer.ubiquity-kvstore-identifier"Entitlement,如果多个不同的应用配置相同的entitlement值,则这些应用都可以共享相同的key-value数据。
使用 NSUbiquitousKeyValueStore 类写入数据到iCloud key-value store,这个类概念上也类似于NSUserDefaults,用于保存和获取简单的数据类型:数字、字符串、日期、数组等等。
应 用的key-value store的容量限制为64KB(而每个键的限制当前也是64KB)。因此应用只能使用key-valuestorage记录很少的数据,不能用于存储用户文档或其它大型数据archive。典型的例子是杂志应用,可以保存用户当前阅读的刊物和页数。这样用户在其它设备打开相同应用时,就能回到之前阅读的位置。
NSUbiquitousKeyValueStore 类绝对无法代替 NSUserDefaults类,应用总是应该通过 NSUserDefaults 将所有配置数据保存在本地磁盘。然后再使用NSUbiquitousKeyValueStore 将需要共享的数据上传到key-valuestore。这样确保iCloud不可用时,你仍然能够访问应用的配置数据。
负责任的iCloudApp
用户的iCloud空间有限,并且由所有应用共享。用户可以查看指定应用消耗的iCloud空间,可以选择删除应用相关联的文档和数据。因此,应用必须对自己存储在iCloud中的文件负责,下面是一些管理iCloud文档的建议: