转载备用

http://blog.csdn.net/leonqiu/article/details/78567600

http://www.iteye.com/topic/1130328

转载自这里:

http://www.cocoachina.com/ios/20160817/17382.html

iCloud 自从随着 iOS 5 推出以来, 经过几次迭代变得越来越完善。 如果你的 App 需要用到文档存储相关的功能,那么 iCloud Document API 是一个很不错的选择。 相比于其他云存储平台,iCloud 和 iOS 设备高度集成,并且 API 的使用更加便捷。 但同时,它的 API 也存在很多雷区,需要我们格外注意。 我们就来看看使用 iCloud API 的正确姿势吧。

正确姿势一 - 检测 iCloud 可用性

在使用 iCloud Document 之前,我们先要检测当前设备是否开启了 iCloud 功能,如果设备本身没有开启 iCloud,我们后续的操作就都会失败。 通过 NSFileManager 来获取 iCloud 的状态:

1
NSFileManager.defaultManager().URLForUbiquityContainerIdentifier(nil)

这个方法接受一个参数, 就是要获取的容器标识。 所谓容器标识, 大多数应用只会用到一个 iCloud 容器,所以我们这里传入 nil, 就代表默认获取第一个可用的容器。

接下来,这个方法内部会查找当前应用拥有的 iCloud 容器, 如果找到就会返回这个容器的 URL, 证明当前应用的 iCloud 容器可用。 如果找不到,就会返回 nil, 证明当前应用的 iCloud 不可用。

这样我们就能根据这个方法的返回值来片段当前设备开启了 iCloud 服务。 只有在服务开启的时候,后续的操作才能进行。

这个方法获取的只是 iCloud 容器的根目录 URL, 我们大多数情况是不使用根目录的, 我们应该使用 Documents 目录, 所以这个方法还需要修改一下:

1
2
3
4
5
6
func getiCloudDocumentURL() -> NSURL? {
     if  let url = NSFileManager.defaultManager().URLForUbiquityContainerIdentifier(nil) {
         return  url.URLByAppendingPathComponent( "Documents" )
     }
     return  nil
}

这样, 判断 iCloud 可用性以及获取目录 URL 的逻辑就都完成啦。 在实现具体逻辑的时候, 使用这个方法获取 URL, 如果能够获取,就可以进行下一步的文件列表操作了。 如果获取失败,就表示当前设备的 iCloud 服务不可用,或者当前 App 的 iCloud 服务没有开启, 这时候可以给用户一个提示, 去设置 iCloud。

最后一个小 Tip, iCloud 容器和你 App 文件沙盒, 在 iOS 文件系统中其实是分别存放在两个不同的地方的:

iCloud 文件路径格式 file:///private/var/mobile/Library/Mobile%20Documents/iCloud~com~xxx~aaa/Documents

App 沙盒文件路径格式 file:///var/mobile/Containers/Data/Application/3B4376B3-89B5-3342-8057-3450D4224518/Documents/

由此可见, 这也是为什么 iCloud 和 Sandbox 文件路径访问需要两套不同的方式的原因了。

正确姿势二 - 获取 iCloud 文件列表

iCloud 的另外一个陷阱就是文件列表的获取。 如果你有过 iOS 开发经验, 那么当得到了一个目录 URL 的时候, 你可能会想到这样得到目录中的文件列表:

1
2
3
if  let documentURL = getiCloudDocumentURL() {
     NSFileManager.defaultManager().contentsOfDirectoryAtURL(documentURL, includingPropertiesForKeys: nil, options: NSDirectoryEnumerationOptions.SkipsHiddenFiles)
}

从代码上看起来似乎没什么问题, 但如果你将这段代码用到 iCloud 文件的操作上, 很快你就会发现问题了。

这还要从 iCloud 在 iOS 系统上的运作机制说起。 其实 iCloud 的所有文件同步操作都是用过驻留在系统的一个进程进行的。 也就是说你的 App 所对应的 iCloud 目录,除了你的 App 进程会操作它, iCloud Daemon 也会操作它。 这就会带来并发访问资源的管理问题。

但这还不是全部,还有一个更好玩儿的。 假如你现在是用是 Mac 笔记本,那么其他设备只要向 iCloud 容器中添加新的文件,你的 iCloud Daemon 进程就会自动的将它们下载下来。

但在 iOS 系统中, iCloud Daemon 因为手机耗电以及网络流量等考虑, 是不会自动下载其他设备新添加到容器中的文件的。 只有你请求打开某个文件的时候才会去下载它的内容。

相信经过我这么一说,大家就察觉到问题了, 如果使用上面那种遍历目录的方法。 对于那些从其他设备添加,并且还没有下载到本地的文件,就会遍历不到了。很显然, 这不是我们期望的结果。

那么在 iOS 上面, 我们怎么取得完整的文件列表呢? iCloud 在 iOS 上虽然不会自动下载这些新添加的文件,但会将这些新文件的元信息(MetaData)传输过来,比如文件名,文件尺寸,修改时间等等。也就是说我们需要查询文件元信息的列表,就可以得到和服务端同步的文件列表了。

综上所述, 获取 iCloud 文件列表的正确姿势是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let metaQuery = NSMetadataQuery()
func listFile() {
     metaQuery.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
     metaQuery.predicate = NSPredicate(value:  true )
     NSNotificationCenter.defaultCenter().addObserver(self, selector:  #selector(listReceived), name:NSMetadataQueryDidFinishGatheringNotification, object: nil)
     NSNotificationCenter.defaultCenter().addObserver(self, selector:  #selector(listReceived), name:NSMetadataQueryDidUpdateNotification, object: nil)
     metaQuery.startQuery()
}
func listReceived() {
     let results = metaQuery.results
     for  item  in  results {
         let fileURL = item.valueForAttribute(NSMetadataItemURLKey)
     }
     NSNotificationCenter.defaultCenter().removeObserver(self, name: NSMetadataQueryDidFinishGatheringNotification, object: nil)
     NSNotificationCenter.defaultCenter().removeObserver(self, name: NSMetadataQueryDidUpdateNotification, object: nil)
     metaQuery.stopQuery()
}

这段代码篇幅稍长, 首先我们初始化了一个 NSMetadataQuery 实例, 然后在 listFile 方法中设置它的属性。

metaQuery.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope] 这个属性表示我们要查询 iCloud 的 Documents 目录中的文件列表。

metaQuery.predicate = NSPredicate(value: true) 这个是对结果集的过滤选项, 我们这个 Query 默认接受所有文件。

NSMetadataQueryDidUpdateNotification 和 NSMetadataQueryDidFinishGatheringNotification 这两个通知分别表示得到查询数据的更新,以及得到全部查询数据。

接下来 listReceived 方法处理这两个通知, 这时候可以去到 metaQuery 的 results 属性,代表我们查找到的文件元信息列表, 最后使用 item.valueForAttribute(NSMetadataItemURLKey) 这样方法就可以得到包括文件 URL,文件尺寸,修改时间这些信息了。

在获取完相关的信息后, 我们可以调用 metaQuery.stopQuery() 方法结束查询操作。 并且 NSMetadataQuery 除了提供我们刚才这种一次性查询之外,还提供一个长期驻留查询的机制, 只要它的查询条件所覆盖的内容发生了变化,就会发送通知给我们。

但要注意一点, NSMetadataQuery 查询操作只能在我们 App 进入前台的时候开启, 也就是说当我们的 App 切换到后台的时候, 要记得暂停查询操作。

正确姿势三 - 使用 UIDocument

之前的文章中,我们讨论过一次关于 UIDocument 的内容,大家可以点击这里 回顾一下。

对于 iCloud 相关的文件操作,最好要使用 UIDocument 来进行。

为什么要使用 UIDocument 而不是直接通过文件操作 API 来进行呢? 这要从咱们刚才说到的进程间资源共享说起。

首先切记一点, iCloud 容器中的文件不止你的 App 在操作它。 还有另外一个叫做 iCloud Daemon 的家伙也在操作它。

这种多个进程共同操作一个资源的时候,就需要保证在同一时刻只有一个进程会操作这个资源。 如果两个进程同时操作这个资源,就会造成非常危险的后果。

比如你的 App 正在把你刚刚修改的内容写入一个文件, 而这个时候你的 iCloud Daemon 有可能将服务端对这个文件的改动也写入进来。 这样,你们最终的结果肯定会是其中一个操作覆盖了另一个操作。

这就需要一个同步机制, 当你的 App 进程在进行写入操作的时候, iCloud Daemon 会进行等待,当你写入完成后, 它才会将服务端的改动也同步过来。

当然了,上面这个简单例子只是为了让大家对资源的安全访问有一个直观的理解,在实际的情况要比我描述的这种更加复杂。

回到我们开始的讨论,类似 NSFileManager 这样的 API,是不能够保证多个进程之间这种安全访问机制的。 所以 iOS 引入了两个类 NSFileCoordinator 和 NSFilePresenter。

给大家一个直观的描述,假设有4个人同时操作一个文档, 每个人都会得到一个 NSFileCoordinator 和 NSFilePresenter。 假设其中一个人要给这个文档中加两行字,他先要用他自己的 NSFileCoordinator 发出通知给其他三个人。

其他 3 个人的 NSFilePresenter 会接收到这个通知,每个在这个时候都可以通过 NSFilePresenter 进行一些准备工作,当这些准备工作完成后,继续通过 NSFilePresenter 告诉通知的发起方,准备完成。

只要这三个人都发出了准备完成的通知后, 第一个发起者才能把这两行字写上去。

描述的比较直接~ 这也就是 NSFileCoordinator 和 NSFilePresenter 的基本原理,通过这个方式保证文件在多个进程键的访问安全。 关于这两个类的实际操作还会更复杂些,咱们在这里先做一个简要的了解。

iCloud 的官方文档中其实是强制要求使用者对文件的操作都通过 NSFileCoordinator 和 NSFilePresenter 来进行的。

但文件操作的逻辑其实很多, 而且这两个类的使用其实相对复杂, 如果不熟悉用错的话可能还会造成调试困难。所以基于这些原因,UIDocument 才浮出水面。这也是 UIDocument 最重要的好处。它的内部已经对 NSFileCoordinator 和 NSFilePresenter 做了封装,我们直接使用就好。

我们通过文件的 URL 即可初始化 UIDocument:

1
let document = UIDocument(fileURL: fileURL)

初始化完成后, 我们直接打开即可:

1
2
3
document.openWithCompletionHandler { success  in
     document.contents
}

UIDocument 会区分 Sanbox 和 iCloud 进行相应的处理, 并且处理多进程操作的问题。 调用完 openWithCompletionHandler 之后, 我们的 UIDocument 相当于已经打开的 NSFilePresenter, 如果其他进程要修改这个文件,我们就会接到通知,并进行准备工作, UIDocument 已经给我们提供了默认的实现。

当文档使用完毕后,可以调用:

1
2
document.closeWithCompletionHandler { success  in
}

这个方法除了关闭文档之外,还会自动为我们处理文件保存操作,以及释放 NSFilePresenter 的占用。

最后, UIDocument 我们不能够直接使用, 还需要实现两个方法:

1
2
3
4
5
6
7
8
9
class Doc : UIDocument {
     var  fileContents: String =  "" ;
     override func contentsForType(typeName: String) throws -> AnyObject {
         return  fileContents.dataUsingEncoding(NSUTF8StringEncoding)!
     }
     override func loadFromContents(contents: AnyObject, ofType typeName: String?) throws {
         fileContents = NSString(data: contents as! NSData, encoding: NSUTF8StringEncoding) as! String
     }
}

UIDocument 虽然为我们实现了很多底层操作, 但如何获取文件内容的逻辑,还是留给了我们自己来实现。 contentsForType 和 loadFromContents 都是回调方法。 contentsForType 方法用于保存文件时提供给 UIDocument 要保存的数据, loadFromContents 用于 UIDocument 成功打开文件后,我们将数据解析成我们需要的文件内容,然后再保存起来。

之所以这样做, 我理解应该是 UIDocument 只是对通用文件的一个抽象。 可以是普通文本文件, 但也可以是其他类型的文件格式, 所以它传递给我们的就是一个原始的 data 数据,如何解析和处理这个数据,就交给了我们自己。

这里对 UIDocument 的基本使用给大家做了一个介绍, 更详细的使用方法大家就需要参考相关文档了。

结尾

自己也曾经开发过 iCloud 相关的功能, 刚开始总会莫名其妙的陷入一些陷阱当中, 出现莫名其妙的错误。 于是呢,花了几天时间好好研究了一下 iCloud 相关的文档。 在过程中发现 iCloud 的文档分布非常多, 从设计规范,到基于文档的编程规范,等等。散步在很多个主题文档中。所以只有把他们全都融汇起来才能慢慢的理解这套 API 背后的机制。

所以呢,这里我把我看到认为重要的地方做了一个梳理。也希望能够帮助大家少走弯路,抓住重点。

iCloud的使用方法相关推荐

  1. 苹果手机 不要删除服务器邮件,iCloud邮件无法删除怎么办?解决iCloud邮件占用空间方法...

    iCloud邮件删不掉怎么办?不少果粉在iCloud管理空间中发现了邮件占用了不少空间,但不能删除,对于这种情况我们该怎么办?今天小编就来分享下清理iCloud邮件占用空间方法,一起来看看吧. 解决i ...

  2. mac 无法连接到 windows 计算机.,每次登录都出现“此mac无法连接到iCloud”的解决方法...

    最近mac出现了个问题,就是开机之后出现"此mac无法连接到iCloud"的问题,试了比较多的方法也没搞定,后来在网上搜到的一个方法,试了一下,搞定了,现在分享出来:(省的以后再乱 ...

  3. linux自动下载icloud,在Linux系统上安装和使用iCloud的方法

    本文介绍在Linux操作系统上安装和使用iCloud的方法.尽管在Linux平台上有很多人使用iPhone,但Apple并没有认真对待它的Linux用户,由于他们的疏忽,没有一种在Linux上使用iC ...

  4. ICLOUD储存空间要升级吗_苹果iCloud照片恢复步骤

    苹果手机照片怎么恢复?照片是我们保存美好回忆的最直接方法,然而,把照片储存到手机里会占用很多空间.所以在清理空间时往往会很容易误删一些重要照片,当然,也有很多小伙伴养成了iCloud备份的习惯,那么今 ...

  5. icloud备份微信聊天记录怎么恢复

    目前市面上苹果手机非常受广大用户的青睐和使用,那么在苹果手机上iCloud是每个手机都拥有的功能,用户可以通过iCloud将手机重要备份数据都传输到云空间中,这样无论是换新的iPhone手机还是还原手 ...

  6. 显示icloud服务器超时,iCloud连接超时验证失败怎么办

    icloud储存空间已满怎么解决 很多小伙伴在使用苹果设备的过程中收到icloud储存空间已满的提示,这个时候应该如何解决呢?相关的解决方法介绍小编已经为大家准备好了.下面,就跟随玩游戏网的小编一起继 ...

  7. 数据丢失不用怕 iPhone手机数据备份方法分享

    现如今,手机已经成为我们生活中不可缺少的一部分,使用手机的人群无非两种,安卓手机和苹果手机.但根据我多年在论坛瞎逛发现,大多人其实买了苹果手机后,并不会使用苹果自带的两种备份工具"iClou ...

  8. 如何通过将照片从 Mac 移动到 iCloud 来节省空间

    如果您发现您的照片库开始占用 Mac 硬盘驱动器上的太多空间,那么开始使用 iCloud 来存储它们可能是个好主意.这有助于减少这些图像占用的空间,同时还可以确保它们在您的 Mac 发生灾难时安全地保 ...

  9. 如何彻底删除软件的 Icloud 数据(Mac / iOS)

    在苹果的软件生态中,icloud 是非常重要的一项,很多 APP 的都使用了 icloud 来备份或同步数据,让我们可以在多个设备上无缝使用这些软件,不过默认情况下 icloud 只有 5 G 的免费 ...

最新文章

  1. ajax更改dom,javascript – 用Ajax响应替换DOM节点
  2. Qt状态机框架介绍(一)
  3. c语言iota怎么用,C++ std::iota用法及代码示例
  4. Python3.7.3安装(Ubuntu16.04)
  5. 蒜黄香菇炒肉 【原创】
  6. leetcode题解5-最长回文子串
  7. Linux虚拟机连不上网克隆虚拟机网卡无法启动
  8. easyui中清空filebox的值
  9. 什么是 JScript?
  10. Guava-Splitter
  11. Oracle 数据库访问故障(TNS-12535、TNS-00505)解决思路
  12. Android常用的开源库收集(持续更新中)
  13. rails集成devise
  14. For菜鸟文章:PE文件格式,qduwg翻译
  15. n阶奇数魔方阵c语言编程,n阶魔方阵C语言
  16. 注意这是ACfly TI芯片版本的程序
  17. 小白成长记(三、SqlSugar的sum用法)
  18. MySQL数据库 sql语句及其含义
  19. 一对一视频聊天app源码,归并排序模板
  20. EasyNVR网页摄像机直播方案H5前端构建之:区分页面是自跳转还是分享依据

热门文章

  1. STM32的TAMPER-RTC管脚作为Tamper使用 - 防拆机
  2. 管理类书籍的一些感悟
  3. slf4j中如何进行log4j配置呢?
  4. 求组合数(c(m,n))
  5. 哈佛医学院退出US News排名,此前已有耶鲁哈佛等顶尖法学院「退群」
  6. IoC和AOP的理解
  7. 成为优秀领导者必备的五个能力要素
  8. 王者荣耀3月31日服务器维护,王者荣耀3月31日体验服停机更新公告 新增派对大作战玩法...
  9. Vue proxyTable
  10. php 获取浏览器指纹,浏览器指纹保护器,可以修改plugs,语言,mimeTypes