CloudKit,是苹果最新推出的基于iCloud的一个云端数据存储服务,提供了低成本的云存储并能做为一个后端服务经过用户们的iCloud帐号分享其应用数据。web
CloudKit主要由两个部分组成:数据库
一个仪表web页面用于管理公开数据的记录类型。编程
一组API接口用于iCloud和设备之间的数据传递。swift
CloudKit也具备安全性,为用户的私人数据提供了完整的保护。而开发者不只只能接入本身的数据库,也不容许查看用户的私有数据。后端
CloudKit适用于那些在服务端计算量不大,却须要使用大量数据的iOS平台独占应用。xcode
这篇教程将带领你经过构建一个叫作BabiFüd的找餐厅应用获取到有关CloudKit开发的实战经验。缓存
提示:这篇教程的实例要求你有一个激活的iOS开发者帐户,不然你将没有使用iCloud和CloudKit的权限。安全
为何选择CloudKit?服务器
在最开始,你也许会好奇为何你要选择CloudKit而非Core Data、商业后端服务或者用本身的服务器。网络
答案有三方面:易操做性、可靠性、成本。
易操做
与其余的后台解决方案不一样,CloudKit的设置至关容易。你无需选择、配置、安装服务或者为缩放比率和安全性之类的问题焦虑。
简单的注册成为iOS开发者你就能够拥有使用CloudKit的资格,而不须要为这个附加的功能单独注册或者从新申请帐号。做为启动CloudKit功能的一部分,全部必要的设置都难以想象的在服务器上自动完成了。
CloudKit的导入方式和其余的iOS框架同样不须要额外下载运行库而且配置它们,框架自己提供了方便的API接口使通常的操做变得极为简易。
对于用户而言,易操做性也是可体现的。因为CloudKit采用了iCloud的证书认证,一旦用户安装以后(或者经过设置应用)就能直接进入,因此创建一个复杂的登陆界面是彻底没有必要的。只要用户登陆上去,那就能轻松使用你的应用了。
可靠
CloudKit的另外一大优点在于,只要用户在开发者和苹果之间更愿意相信苹果,那么他们就没必要为本身私密数据的安全感到担心,由于CloudKit隔离了用户数据与和开发者。
虽然对于开发者来讲缺少数据会有些失望(好比在调试的时候是很须要数据的),不过对于他们还是有好处的——至少不用再担忧安全性,也不用去说服用户信任你。甚至他们丝毫没有意识到CloudKit和iCloud之间的区别,只要他们信任iCloud就意味着他们信任你。
成本
最后,对于每一个开发者来讲运行一个服务须要至关大的耗费。就连是最便宜的主机服务也不会考虑你应用的价格来订价,并且只要跑上哪怕一个应用,你就得掏钱。而CloudKit容许你免费的使用必定限额的空间来存储公开数据,若是想了解更多你能够看这里。
这些优点使得CloudKit能够简单无脑的服务于Mac或者iOS应用。
关于BabiFüd
本教程的示例程序是一个叫BabiFüd的应用,是一款最新的定位服务式的典型应用。总之,相较于在定位后侧重于餐厅的食物质量、服务效率、价格以及儿童方面的用户定位,这个应用更关注餐厅设施的变化、座位的温馨度和健康饮食。
这款应用包含了四个标签:附近地点的列表(Nearby),显示附近地点的地图(Map),用户生成的记录(Note)和应用设置(Setting)。你也能够经过如下的两张截图来感觉如下这个应用大概是什么样子:
这个应用的模型架构是在这些显示视图的内部调用了CloudKit,在这里CloudKit对象称为记录(records),模型中的主要记录类型称做Establishment,表明了你的应用里那些各类各样的地点。经过这种方式你还能够在你的数据库中添加相关的评分和备注信息。
开始
开始教程以前你须要下载一个CloudKit项目。
在你开始编码以前你能够更改你应用的Bundle Identifier和Team,你须要设置一个Team以后苹果才会给你提供CloudKit相关的功能使用权,而后一个惟一的Bundle Identifier会使得整个过程轻松许多。
像下图这样,在Xcode里面打开BabiFud.xcodeproj。选择项目导航里的BabiFud项目,而后选择目标BabiFud以后在Bundle Identifier里填入一个你以为不会重名的名称,我推荐使用域名反序法,而后加上项目名称,而后选择一个合适的Team。
请留意Bundle Identifier和Team。如今你须要为你的应用设置CloudKit服务,而后建立一个容器来存储你的应用数据。
权限和容器
在你经过应用添加任何数据以前你须要一个容器来存储这些数据记录。所谓的容器其实就是在服务器上为应用数据假想一块存储空间,由共享数据和私密数据组成。建立容器意味着你的应用须要有使用CloudKit的权限。
在目标编辑器里选择Capabilities标签,而后启用iCloud,以下图:
这个时候Xcode可能会提示你登录你已绑定开发者帐号的苹果用户,你只须要照作就是了。最后,在服务选项里面勾上CloudKit选框。
这样你就建立了一个名称形如 iCloud.<你的应用的bundle id="">的默认容器。示例以下图:
若是你在上面这些步骤碰见了什么问题或者错误,这里有一些状况的解决方案:
1.在启用iCloud那一步若是提示了错误或者警告的话,你能够试着点击最Fix Issue按钮,这样可能会须要等一段时间。
2.bundle id和iCloud容器必须一一对应,好比你的bundle identifier写成了“com.<你的域名>.Babifud”,那你的容器名应该是"iCloud.com.<你的域名>.Babifud"。
3.容器的命名必须独一无二,由于这是CloudKit用来接入数据的惟一标识符,因此这也意味着bundle id也必须独一无二。
4.为了确保拥有正常工做的权限,应用id和bundle id必须列在证书、标示符和配置中心的App ID部分。也就是说你须要设置一个Team id以用来验证登陆,此外也要列上应用id,还有iCloud 容器的id。
一般来讲CloudKit会自动为你完成全部内容,前提是你登入的是一个可用的开发者帐号。不过有时候并非那么及时的同步,你还能够通用一个新的帐号,而后改变CloudKit容器id保证对应。另外,为此你可能须要修改info.plist文件或者BabiFud.entitlements文件以确保里面的id也和你设置的bundle id匹配。
关于CloudKit仪表盘
设置了可用权限以后下一步就是配置一下CloudKit,来建立应用数据的记录类型。以后你即可以开始使用CloudKit的仪表盘,以下图,点击CloudKit Dashboard。
提示:你也能够用网页登录你的仪表盘。
你会看到仪表盘出现了,像这样:
下面是此教程须要的有关于仪表盘的一些概述:
左边栏的SCHEMA表明CloudKit容器的高级类:Record Types, Security Roles, 和Subscription Types。在教程里你只会用到record Types。
一个Record Types用来设置定义一个单独的记录。在面向对象编程里,Record Types就至关于某一对象的类模板。一个记录能够看作一个Record Type类的实例。这是容器的基本数据结构,却是很想数据库里面的一行数据,包含了一系列键和值。
PUBLIC DATA和PRIVATE DATA 就是你添加查找数据的地方,你须要接入数据库。记住,做为一个开发者你能够阅览全部的共享数据,不过你只能看到你本身的私密数据,User Records记录了一些当前CloudKit使用者的的信息好比名称或者邮件。一个Record Zone(这里是Default Zone)用于给私密数据分组提供数据的逻辑结构。当在进程的其余操做以前容许大量数据数据同时存储时Custom zones支持自动处理。不过那不是咱们今天要讨论的范围。
ADMIN栏主要用于管理开发团队的成员权限,若是你的项目有不少人共同开发,你能够规划一下他们各自的权限,这个问题比较艰深并且有些超纲故而在此不谈。
添加Establishment类的记录
选中Record Types,点击左上方+的图标而后你就能够添加一个新的记录类型并设置细节。以下图:
命名你的新记录类为Establishment。
来考虑一下有关应用的设计,每个你想要记录的Establishment都有不少数据:名称、地点、或者是儿童友好的选项都是有用的。记录类就是用于定义记录中各式各种的数据的。
当你开始定义Name, Attribute Type,和Index的时候你会看到以下这行,这个时候一个Attribute命名模板已经自动建立。
你能够修改这个预设的Attribute Name为你想要的,这里由于要作的是示例中的应用,所以你须要添加下面的Attribute,点击Add Attribute...添加新行。
当你完成以后你会获得以下这样一个属性表单。
点击页面底部的Save以保存你的新纪录类型。
你如今能够添加一些Establishment样本到你数据库里面了。
选择左边导航里的Default Zone栏这里主要存储了你应用上的共享数据,而后在出如今中间的下拉列表里选择Establishment记录类,最后点击右边+的图标。
这样你就建立了一个全新的空Establishment记录。
这时你能够添加一些测试数据了。
下面这个测试样例的数据是彻底虚构的,位置数据已经被设定在苹果总部附近以便于在虚拟机上测试。
填写数据以下表:
提示:每个 CoverPhoto属性里的图片都存储在项目的Supporting Files\Sample Images文件夹里面,你只要拖动这些图片到 CoverPhoto栏里面它们就在记录保存的时候会自动上传。
三个测试数据录入完毕以后会像下面这样;
对于每条数据,这里的值只是表明它在数据库中的样子,在应用的那一端会彻底不同,好比SeatingType和ChangingTable是枚举类型,在这里的这个整数表明的是枚举的数的值。对于HealthyOption和KidsMenu来讲这里的值表明了布尔型数据:0表明这家餐厅不知足此项,1则是知足。
让咱们在退回Xcode。是时候把这些数据整合进你的应用了。
查询Establishment数据
CKQuery对象被用于从数据库里查询记录。一个CKQuery描述了如何查询一些特定类型或是特定条件的记录。这些条件能够是诸如“记录的Name是M开头的”、“有软垫座位的记录”、“方圆三千米之内的记录”。这一类型在Cocoa里的表达方式则是使用NSPredicate对象,NSPredicate也会断定各对象是否知足规则。在Core Data里适用的不少断定很一样适用于CloudKit。由于断定条件一般会被定义为对某一属性进行比较。
CloudKit仅支持可用的NSPredicate方法中的一部分子集。包括一些数学比较,字符串和集合运算,以及新增的特定距离函数。distanceToLocation:FromLocation方法:NSPredicate为CloudKit添加的一个根据已知坐标计算和半径匹配范围内记录的方法。下面的这类断定都略过了细节,对于其它的查询CKQuery类引用了一个关于使用的方法和如何使用的清单。
提示:CloudKit包括了支持CLLocation类,其中有Core Location框架下包括地理位置的一些对象。这使得在某一地理范围内查询Establishment类变得简单,你不须要写那些痛苦的数学公式。
打开Model\Model.swift文件,包含了服务端的全部调用。
用下面这一段替换掉fetchEstablishments(location:, radiusInMeters:)方法
1 func fetchEstablishments(location:CLLocation, 2 radiusInMeters:CLLocationDistance) { 3 // CloudKit在它自带的距离断定中使用的单位是千米,这里把radiusInMeters转换成千米 4 let radiusInKilometers = radiusInMeters / 1000.0 5 // 这一断定Establishment类的条件是它们到当前距离的千米数,这个方法会根据用户当前位置以及设定范围获得范围内全部的Establishment和它们的位置信息 6 let locationPredicate = NSPredicate(format: "distanceToLocation:fromLocation:(%K,%@) < %f", 7 "Location", 8 location, 9 radiusInKilometers) 10 // CKQuery 对象的建立须要一个record类型和一个断定条件做为参数,它们将用于查询 11 let query = CKQuery(recordType: EstablishmentType, 12 predicate: locationPredicate) 13 // performQuery(_:, inZoneWithID:, completionHandler:)方法会把你的查询发送到iCloud,返回结果。当传递的inZoneWithID为nil的状况下你只会在default zone也就是共享数据中进行查询,若是你但愿连着一块儿查询私人数据的话,则须要进行一个单独的调用。 14 publicDB.performQuery(query, inZoneWithID: nil) { 15 results, error in 16 if error != nil { 17 dispatch_async(dispatch_get_main_queue()) { 18 self.delegate?.errorUpdating(error) 19 return 20 } 21 } else { 22 self.items.removeAll(keepCapacity: true) 23 for record in results{ 24 let establishment = Establishment(record: record as CKRecord, database: self.publicDB) 25 self.items.append(establishment) 26 } 27 dispatch_async(dispatch_get_main_queue()) { 28 self.delegate?.modelUpdated() 29 return 30 } 31 } 32 } 33 }
全部的都作完以后你也许会好奇CKDatabase的实例publicDB是从哪儿来的。那么看下Model类最开始的代码:
1 let container : CKContainer 2 let publicDB : CKDatabase 3 let privateDB : CKDatabase 4 5 init() { 6 // defaultContainer()表明的是你在iCloud功能栏里制定的那个容器 7 container = CKContainer.defaultContainer() 8 // publicCloudDatabase则是你应用上的全部用户共享的数据 9 publicDB = container.publicCloudDatabase 10 // privateCloudDatabase仅仅是你我的的私密数据 11 privateDB = container.privateCloudDatabase 12 }
这些代码会从共享数据里找出几个当地的Establishment数据来,不过咱们还须要在应用里面用一个视图控制器来显示点东西。
设置必要回调函数
你可能会注意到通知中心使用的是咱们很是熟悉的委托模式。下面是Model.swift文件最开始的协议部分代码,你能够在你的视图控制器中实现它。
1 protocol ModelDelegate { 2 3 func errorUpdating(error: NSError) 4 5 func modelUpdated() 6 7 }
打开MasterViewController.swift文件并用下面代码替换掉 modelUpdated()方法:
1 func modelUpdated() { 2 3 refreshControl?.endRefreshing() 4 5 tableView.reloadData() 6 7 }
这里一般是在新数据可用时执行。在tableView(_: cellForRowAtIndexPath:)中,有关CloudKit对象的全部table view cell的唤醒都被照顾到了,你能够本身随便看一看。
如今来用下面的代码替换掉errorUpdating(error:):
1 func errorUpdating(error: NSError) { 2 let message = error.localizedDescription 3 let alert = UIAlertView(title: "Error Loading Establishments", 4 message: message, delegate: nil, cancelButtonTitle: "OK") 5 alert.show() 6 }
不管查询结果产生任何错误都会调用此方法。这些错误发生的缘由多是网络状况较差,也多是像用户凭证丢失或出错这样的CloudKit特有问题,还多是由于你所要找的这条记录根本不存在。
提示:在对待任何远程服务的时候一个好的异常处理是必不可少的,而在这里你仅仅是给用户弹出了一个错误提示而已。
在模拟器上运行一下,你会看到以下这样一个关于附近的Establishment列表:
你能够看到Establishment的名称以及它提供的相关服务,不过你却没有看到任何相关的图片显示,这是为何?
当你获取你的Establishment记录时候你也会自动的去获取图片,不过,想要在你的App中显示图片的话你还须要作一些必要事项(这在下面会提到,先来看一看一些出错处理)。
常见错误处理
若是你获取的列表显示并不正确,请确保你在Debug\Location\Apple中所设定的位置信息准确无误。若是你更改了位置信息,下拉列表进行强制刷新,而不要等待位置触发器。
若是你是在启用了定位的iPhone或者iPad上调试,并且列表显示仍不正确,那仅仅是由于你这些Establishment的位置离你的当前位置还不够近。这里有两种解决方法:要么把示例数据的位置信息修改到你附近,要么是用模拟器来调试。其实这里还有一个实用性卓越的第三种解决方案——你能够跑到Cupertino去而后在苹果的草坪上散散步。
若是示例数据并未显示适当,或者根本不显示。用CloudKit仪表盘检查一下示例数据,首先确认你是否将它们都添加进了Default Zone之中,而后看一看它们的值是否正确。若是你想从新写入数据的话你能够像下图这样删除掉原来的数据:
有时候调试CloudKit,报错会至关狡猾。好比在写入的时候,CloudKit的报错并不会包含太多信息,若是你想断定错误缘由的话你须要看一下错误代码已经你具体要作的数据库操做是什么样的。使用数值化的错误代码,而后再跟CKErrorCode对比来检错。文档中的标题和描述会对错误范围的缩小有所帮助,下面是一些例子:
提示:对于一些简单的错误,你能够在苹果的官方文档中有关CloudKit的章节里CKErrorCode枚举类型中找到。
如下是常见的错误类型,以及处理建议:
.BadContainer 和 .MissingEntitlement
检查一下在iCloud的Entitlements栏目中指定了与CKContainer对象匹配的容器,而且它存在于你的CloudKit仪表盘中。
.NotAuthenticated 和 .PermissionFailure
确保你在Settings.app中输入了正确的iCloud用户凭证,并确保iCloud可用。
.UnknownItem
检查CloudKit仪表盘中的记录类型名称与RecordType字串相匹配。
使用二进制资源
资源就是二进制数据,好比在你的记录中使用的图片。在这个例子中,你的应用资源就是那些将要展现在你附近的列表视图中的Establishment照片。
在这一节你将添加有关资源加载的逻辑,首先这些资源须要在你重获Establishment记录的时候已经下载好了。
打开Model\Establishment.swift文件而且使用下面代码替换掉loadCoverPhoto(completion:)方法:
1 func loadCoverPhoto(completion:(photo: UIImage!) -> ()) { 2 //虽然资源数据是在你获取记录的剩余内容时一块儿下载的,可是你并不想在同时显示这些图片。因此这里把全部的代码都包裹到了dispatch_async块中。 3 dispatch_async( 4 dispatch_get_global_queue( 5 DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)){ 6 var image: UIImage! 7 //资源在CKRecord中做为CKAsset的一个实例来保存,以方便准确的转换 8 let coverPhoto = self.record.objectForKey("CoverPhoto") as CKAsset! 9 if let asset = coverPhoto { 10 //使用资源提供的本地文件URL来加载图片 11 if let url = asset.fileURL { 12 let imageData = NSData(contentsOfFile: url.path!) 13 //使用资源数据来构建一个UIImage实例 14 image = UIImage(data: imageData) 15 } 16 } 17 //使用获取的image来运行completion回调函数 18 completion(photo: image) 19 } 20 }
构建并运行,Establishment图片应该能够显示了:
下面是有关CloudKit资源我已经给出的两点:
1.资源数据在CloudKit上只能以一个属性的形式存在于记录中,你不能单独的存储它们,删除一条记录也会删除掉相关的全部资源数据。
2.由于资源数据的获取是与记录剩余内容的获取在同时进行的,因此也会在性能上带来必定的负面影响,若是你的App会用到大量的资源,你应该将资源单独存储为另外一种形式的记录。
更多
如今这个App能够在表格视图中下载到有关Establishment记录,并加载细节信息和图片了。你能够在最下方下载到一个完整的工程文件,而后对其进行以下扩展:
容许用户上传图片,记录,评论或者投诉。分享一些糟糕的经历会对用户有所帮助。
让用户使用地图来添加新的Establishment记录,这一功能添加到你的Model类后可做为一个关于在共享数据库或者私人数据库中存储记录的示例。
添加过滤盒搜索,Model类可用距离断定构建一个CKQuery,不过断定能够改进得更为复杂。CloudKit也支持文本查找和特征字串。
改善App和数据加载的性能。这个教程只是在一切准备就绪以后使用了便捷可用的方法来调用一些现成的操做。CKDatabase的实例基于NSOperation以提供了比起API执行更多得多的操做方法。在操做中(好比操做刚刚完成)你就能够接受数据而非同时进行。
使用缓存和同步以保证App可以离线使用并在联网以后就能当即更新到最新的内容。