转载请注明,原文地址:滚蛋吧!服务器 · Begining CloudKit
各位早年大概都听说过Parse这家领先的BaaS提供商,它为移动开发提供强有力的后端支持,包括云存储、数据分析、用户关系等等。不过它的命运大概也就是被FB收购之后被家暴中了李阳神功第九重,以至于一年之后暴毙家中。
什么?你没听说过Parse?没听说过BaaS?那LeanCloud呢?都不知道?好吧,不送了您呢。
今天的猪脚CloudKit,作为Apple在iOS8上推出的基于iCloud的一个云端数据存储服务,提供了低成本的云存储并能作为一个后端服务,通过用户们的iCloud账号分享其应用数据。以上是行话,CloudKit对于移动开发者好处请自行QA or Here。
至于为啥三年后的今天才蹭这个热点,还是归咎于今年正式出道,成了流落街头的个人开发够。
你要问我原因???
又或者。。。
本文你将学习到CloudKit框架的所有方面,包括使用CloudKit Dashboard管理数据库以及创建和修改记录等重要基础知识,还有更高级的功能,如管理数据冲突,共享数据等。
但有一个最大最大的问题,你需要一个真实的并且没有过期的开发者账户,而不是去买个证书就能搞定的。如果你是一个真正的iOS开发者,我相信你即便没有自己的开发账户,也有公司的。如果没有那还是赶紧打住,不要继续浪费生命了。
社会你拉哥,人狠话不多!!!
在此CloudKit教程中,我们将通过创建一个名为BabiFüd的餐厅评级App,以此来实际操作CloudKit。
注意:此CloudKit教程中的Demo,需要一个iOS开发者帐户。否则将无法启用iCloud授权或访问CloudKit dashboard。
本教程的Demo,BabiFüd是一个类似于“速度餐厅”的App。用户根据小屁孩友好度,而不是根据食品质量,服务速度或价格来评价的餐厅。 这里面包括设施新旧,小屁孩餐具和食品的健康程度来作为评价基准。
该应用程序包含四个选项卡:附近餐厅列表,附近餐馆的地图,用户笔记和设置。本教程中我们只使用附近餐馆的列表这个选项卡。下面简单的展示下Demo效果。
模型类通过调用CloudKit来支撑视图。records作为CloudKit对象。Establishment
是在模型中的主要record类型,它代表Demo中的各种餐馆。
不逼逼了,开撸
我们先下载初始工程。
在开始正式编码之前,必须更改工程的Bundle ID
和Team
,就如前面说的,必须要有付费的开发者计划才能获取到CloudKit权限。熟悉iOS开发的童鞋对这个过程肯定是So easy。
打开BabiFud.xcodeproj
,并在Project Navigator
选择BabiFud
工程,接着选择BabiFud target
,在General
选项里替换好Bundle Identifier
,最后选着好有付费开发者计划的Team
即可。
得益于新版Xcode的优势,Team
将自动绑定好Bundle Identifier
,现在,你只需要设置好CloudKit
并且创建一些containers
来保存数据即可。
Entitlements 和 Containers
在添加任何数据之前,我们需要一个Container
也就是官方术语中的容器来保存数据记录,也就是Record
。 Container
在官方术语中是服务器上所有应用程序数据概念位置。目前有三种,Public,Private和Share。
- Public:公有数据,同一个App中所有用户都能访问的数据存储区域。
- Private:用户的隐私数据,只有用户iCloud账户授权的设备才能访问。
- Share:可分享的数据,用于用户之间数据分享。
要创建容器,首先需要启用iCloud授权。如图在target editor选择Capabilities
选项卡。然后将iCloud
权限开关打开。
此时,Xcode可能会提示需要一个已激活开发者计划的iOS开发者帐户来登录。
最后,如图所示启用CloudKit
。
这将创建一个名为iCloud.
的容器,如图所示:
iCloud问题排查
如果在创建,构建项目或运行应用程序时看到任何警告或错误,或者Xcode正在吐槽Container ID
有问题什么的,可以根据以下提示进行故障排除:
-
如果上述步骤操作
iCloud Kit
显示任何警告或错误,请尝试Fix Issue
按钮,Xcode并没有你想象中的那么人工智能,或者需要修复几次。
为避免
Container ID
和Bundle ID
冲突。如,Bundle ID
为com.
,则iCloud里的.BabiFud Container ID
为iCloud.com.
。这个工作Xcode会自动帮你完成,务须担心!.BabiFud 由于CloudKit要访问数据的全局标识符,所以
Container ID
必须唯一(其实Container ID
包含Bundle ID
,App要上架Bundle ID
肯定要唯一,所以也就没什么好说明的了)。如果能确保自己的
Apple ID
是在开发者计划的有效期内,只需要在配置项里选好对应的Team
,Xcode就会自动帮你完成证书和配置文件的一系列工作,如果偶尔抽风了,可以强退,也阔以直接编辑info.plist
或者BabiFud.entitlements
。
CloudKit Dashboard
完成必要的设置之后,下一步就要创建记录类型(Rcord Types)和定义数据了。我们可以直接点击CloudKit仪表盘,如图上位置。
你也可以直接访问地址:https://icloud.developer.apple.com/dashboard/。
新版的界面就长这样,看起来比老版本丑了不是一个数量级,设计组的人怎么想的,赐予你们随意感受下:
新版CloudKit Dashboard包含:Data,Logs,Telemetry,Public Database Usage,Api Access。
- Data:包含了ZONES,RECORDS,RECORD TYPES,INDEXES,SUBSCRIPTIONS,SUBSCRIPTION TYPES,SECURITY ROLES,CloudKit的数据中心入口,本教程暂时只使用Record Types,其他类型后续讲解。
- Logs:查看服务器活动日志,显示数据库操作,推送通知以及对应环境中的其他活动。
- Telemetry:查看对应环境中服务器端性能和数据库利用率的图表,分享和推送事件。
- Public Database Usage:查看公共数据库使用情况的图表,包括活跃用户,请求频率,资产转移和数据库存储。
- Api Access:管理API令牌和服务器密钥,允许对应环境进行Web服务调用。
Record Types:它可以定义了很多字段。如果用面向对象来解释,它就像一个类。一个记录可以看做是一份实例,简单理解的话,它其实就是一个键值对的数据结构集合。
创建Record Type
在这里,搭配我们的Demo需要创建一个名为Establishment
的Record Type
数据模型,企业模型需要很多数据构成:名称,位置和可用性,以及各种适合儿童的选项。除了内置字段以外,我们的自定义字段包含:
- Name:企业名称。
- Location:企业地理位置信息。
- CoverPhoto:企业Logo或者封面。
- ChangingTable:婴儿换衣台类型。
- SeatingType:座位类型。
- HealthyOption:健康指数选项。
- KidsMenu:儿童菜单。
在CloudKit Dashboard
里进入Development
的Data分组,选择RECORD TYPE
栏,如图所示,选择Create New Type
创建新的记录类型:
记住新创建的记录类型名称为:Establishment,千万别弄错了,不然Demo报错找不到对应的记录。
接下来我们添加自定义字段,点击Add Field
,新建Name
字段,类型属性是String,最后点击Save Record Type
保存新建字段。
然后新建索引,切换到INDEXS
栏,选中刚刚新建的记录类型Establishment,点击Add Index
为Name字段添加三个索引,分别是:QUERYABLE,SOTREABLE,SEARCHABLE。
注意,CloudKit Quick Start官方文档没有及时更新,在老版本里面,新建字段会自动新建索引,新版CloudKit Dashboard则不会,需要我们自己按需添加。
剩下的Field字段和Index索引按照下表自行添加:
我们可以一次性点击添加多个字段,你非要浪费生命一个个添加我也没有什么办法来说你。
添加之后基本上就长这样了,如果程序报错,可以回来稍微对照一盘。
一次性添加字段,一定要检查好字段名称和对应的类型属性。如果你弄错了,唯一能改的方法就是删了对应字段,再从新添加。
接下来,就要添加真正的记录了。选中RECORDS
栏,确认好Public Database和Default Zone,然后点击Create New Record…
按钮,创建真正的记录。
接着我们要做到就是添加一些虚拟数据。至于位置信息建议都稍微集中点,这里我们把定位都设在苹果总部附近,方便在模拟器中查看。请根据下图把模拟数据,或者你兴致好自己想也成。
创建完成之后,怎么知道有没有成功呢?得益于我们之前已经建立好索引,现在我们直接选择好记录类型查询即可。
新版本UI不像老版本,创建成功之后会直接显示记录,而是需要根据自己需求查询。
对于每个记录,数据和App里表示可能完全是不同的,例如,SeatingType
和ChangingTable
是structs。因此,座椅类型的Int值可能对应于“高脚椅”或“助推器”座椅。对于HealthyOption
和KidsMenu
,Int值表示布尔类型:0表示建立不具有该选项,1表示它是。
如果你一直在跟着教程走,前期已经把开发者账号准备好,并且工程的证书,配置文件,iCloud报错都搞定之后,我们马上才能开始真正的代码之旅。
如果还有问题,可以对照教程查错或是自行检查。
- 需要有一个可用于开发的iCloud帐户:Creating an iCloud Account for Development.
- 帐户相关联的iCloud凭据:Enter iCloud Credentials Before Running Your App.
你要是操作的够丝滑,现在可以打开Xcode和我们的Demo工程。是时候开始代码狗真正的搬砖工作了!
记录查询
CKQuery
对象用于从数据库中查询记录。它描述了如何查找符合特定条件的指定记录类型的所有记录。它们可以是以M打头的名称字段的所有记录,或是强化座椅的所有记录,亦或者3km内的所有记录“。那查询的表达式当然是用NSPredicate
。谓词也用于Core Data,也适用于CloudKit,毕竟NSPredicate在原生开发里是通用的查询表达式。
但不要高兴的太早,CloudKit仅支持NSPredicate部分函数。 包括常用数学运算和比较,字符串的集合操作(例如”列表中的字段匹配”)和特殊距离函数。至于distanceToLocation:fromLocation:
这种函数已添加到NSPredicate中,以便CloudKit里的位置记录与来自已知位置的指定半径内的位置字段进行匹配。这种类型的谓词将在下面做出详细介绍。对于其他类型的查询,CKQuery类引用包含了详细的函数列表以及如何使用它们的说明。
注意:CloudKit包括对CLLocation对象的支持。使用这种Core Location的坐标对象,更方便的创建两个坐标之间的计算查询,避免你写很多杂乱无章的数学表达式。
好,我们现在点开Model/Model.swift
文件,使用下面代码替换掉fetchEstablishments(_ location:CLLocation, radiusInMeters:CLLocationDistance)
方法的内容:
func fetchEstablishments(_ location: CLLocation, radiusInMeters: CLLocationDistance) {
// 1
let radiusInKilometers = radiusInMeters / 1000.0
// 2
let locationPredicate = NSPredicate(format: "distanceToLocation:fromLocation:(%K,%@) < %f", "Location", location, radiusInKilometers)
// 3
let query = CKQuery(recordType: EstablishmentType, predicate: locationPredicate)
// 4
publicDB.perform(query, inZoneWith: nil) { [unowned self] results, error in
if let error = error {
DispatchQueue.main.async {
self.delegate?.errorUpdating(error as NSError)
print("Cloud Query Error - Fetch Establishments: \(error)")
}
return
}
self.items.removeAll(keepingCapacity: true)
results?.forEach({ (record: CKRecord) in
self.items.append(Establishment(record: record,
database: self.publicDB))
})
DispatchQueue.main.async {
self.delegate?.modelUpdated()
}
}
}
按照编号代码功能如下:
- 1.CloudKit中谓词功能的距离单位是公里,这行代码只是距离单位转换。
- 2.根据当前位置和半径范围,筛选出范围内的餐饮企业,过滤掉不需要的对象。
- 3.根据记录类型和谓词查询条件,创建CKQuery对象用于远程查询。
- 4.最后执行
performQuery(_:inZoneWithID:completionHandler:)
,iCloud将按照查询条件进行数据筛选,在闭包里等查询结果的返回。
inZoneWithID
参数如果不传,那就是默认的公有数据库的DefaultZone。如果想把公有和私有的数据库都进行检索,就必须分开查询。
对于CKDatabase
的实例publicDB,我们可以查看Model.swift
顶部:
let container: CKContainer
let publicDB: CKDatabase
let privateDB: CKDatabase
init() {
// 1
container = CKContainer.defaultContainer()
// 2
publicDB = container.publicCloudDatabase
// 3
privateDB = container.privateCloudDatabase
}
上述我们定义的集中数据库实例:
- 1.默认容器就是你在iCloud Dashboard窗口中看到那些内容。
- 2.公共数据库是你应用程序所有用户共享的数据库。
- 3.私有数据库仅包含属于当前登录用户的数据,此教程暂不讨论。
该代码将从公共数据库检索一些本地的餐饮企业,但必须将其连接到视图控制器才能看到对应内容。
设置回调
这里我们用熟悉的代理模式来处理回调。在Model.swift
顶部实现如下协议:
protocol ModelDelegate {
func errorUpdating(error: NSError)
func modelUpdated()
}
接着打开MasterViewController.swift
,覆盖掉modelUpdated()
的实现:
func modelUpdated() {
refreshControl?.endRefreshing()
tableView.reloadData()
}
当有数据回来时,tableView(_:cellForRowAtIndexPath:)
已经添加好实现,会自动Cell处理刷新。
同样,覆盖掉errorUpdating(_:)
的实现:
func errorUpdating(_ error: NSError) {
let alertController = UIAlertController(title: nil,
message: error.localizedDescription,
preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: .default, handler: nil))
present(alertController, animated: true, completion: nil)
}
当查询产生错误时调用此方法。由于网络太差或CloudKit的某些特定问题(如缺少,不正确的用户权限或查询中没有记录),都可能会发生错误。
对于任何类型的远程服务屌用时,良好的错误处理逻辑是至关重要的。现在,简单处理,只需要向用户显示返回错误的消息即可。
然而,有一种常见问题就是用户没有登录到iCloud或没有为此应用打开iCloud权限。所以你可以修改
errorUpdating(_:)
来处理这种常见情况。提示:这两种错误码CKErrorCode
返回都为1
。
考虑到懒癌患者:
func errorUpdating(_ error: NSError) {
let message: String
if error.code == 1 {
message = "Log into iCloud on your device and make sure the iCloud drive is turned on for this app."
} else {
message = error.localizedDescription
}
let alertController = UIAlertController(title: nil,
message: message,
preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: .default, handler: nil))
present(alertController, animated: true, completion: nil)
}
那么剩下就是让Xcode飞起来,你就能看到你如下列表。
疑难解答
- 如果您使用的是iOS模拟器,并且列表不能正常显示,请通过从Xcode的功能菜单,路径:
Debug\Simulate Location\San Francisco, CA, USA
,中进行选择。确保设置了正确的位置,下拉刷新拉取正确数据,不要干等着浪费生命。 - 如果您使用的是iPhone或iPad等实体机设备,并且启用位置服务,要是列表不能显示,那么肯定是离我们的虚拟餐厅的距离位置还不够近。
现在你有两个选择:把我们的模拟数据位置改到你当前位置的附近,或使用模拟器运行应用程序。要还不行,我建议牵条狗去苹果总部遛弯得了。 - 当然你要想显示的前提是,你真的有数据,如果按照前面的教程做了之后,CloudKit Dashboard里肯定是有完整数据的,前面也说过,如果你要修改数据,唯一方法,删了重建。
在调试CloudKit的时候可能会遇到相当棘手的错误,在做本教程的时候,我并没有遇到什么蛋疼问题,如果你有遇到,可以使用CKErrorCode
的枚举来定位问题,这样免得你像个无头苍蝇一样不知所措。
以下是一些常见错误:
- .badContainer:指定的未知容器或未经授权。
- .notAuthenticated:当前用户未通过授权验证,并且没有用户记录可用。如果用户没有登录到iCloud,可能会发生这种情况。
- .unknownItem:指定的记录不存在。
当我们在拉取餐厅数据的时候,只看到餐厅名字和服务内容,并没有看到餐厅图片,What the fuck!!!
做个网络开发的都知道,不管你是拉取的图片是二进制数据或者是图片地址,都要自行加载显示,所以不要精慌,教程继续。。。
二进制数据资源
接下来该整合二进制数据资源,这里的资源就是我们所需要的图片,当然可以是任何你想要的数据资源,都是以二进制的方式存在数据库里。这里我们需要把二进制的图片加载到MasterViewController
的列表里。
这里我们自己实现相关的图片下载和相关的加载逻辑。
打开Model/Establishment.swift
文件,替换loadCoverPhoto(_:)
方法里的代码:
func loadCoverPhoto(completion:@escaping (_ photo: UIImage?) -> ()) {
// 1
DispatchQueue.global(qos: DispatchQoS.QoSClass.background).async {
var image: UIImage!
defer {
completion(image)
}
// 2
guard let asset = self.record["CoverPhoto"] as? CKAsset else {
return
}
let imageData: Data
do {
imageData = try Data(contentsOf: asset.fileURL)
} catch {
return
}
image = UIImage(data: imageData)
}
}
此方法的作用就是图像延迟加载:
- 我们在下载资源的同时,也可以查询其他记录,基于用户体验的问题,这里我们使用异步下载的方式,所以把下载代码放到
dispatch_async
异步加载的闭包里。 - 数据资源以
CKAsset
实例的方式存放在CKRecord
里,所以需要我们根据存在方式自行转换,这里提供的是File URL的方式进行本地加载。 - 使用
UIImage
的实例方法加载本地二进制图像数据。 - 当加载到图像之后我们会进行回调,请注意,无论执行哪个回调,此
defer block
都将被执行。例如,如果没有图像资源,则图像在返回时不会设置,餐厅不显示图像。
现在再次运行App,看下图像是不是是在列表里已经被异步加载了。
关于CloudKit资源有两个问题:
- 二进制资源只能存在于CloudKit属性为
Assets
记录中。它们不能单独存在,删除记录也将删除任何关联的Assets
资源。 - 检索
Assets
资源可能造成性能影响,因为二进制资源与其他记录数据是同时在下载。如果您的应用程序大量使用Assets
资源,那你就应该单独新建一个记录来存储这个Assets
资源,并且与对应的记录数据的引用进行关联。
结尾
该Demo已经可以下载已经存在的记录,并且完整的显示数据信息和图片。
如果按照教程操作到最后,Demo并不能达到像我这样的效果,那我给你提供最终的完整工程,请自行检查。
最后,你要是我给你几点建议完善这个Demo:
- 用户可自行添加添加照片,备注,评论和投诉。
- 用户可使用地图创建新的记录,
Model
类中的函数已经有了将记录保存到公共或私有数据库的示例代码。 - 添加过滤和搜索,我们可以通过一个更复杂的距离谓词查询条件来进行精确筛选过滤。CloudKit还支持对字符串的文本搜索。
- 提高应用程序的性能和数据加载体验。
CKDatabase
是基于NSOperation
的实例,可以使用队列的方式来优化性能。 - 提供缓存和数据同步,使应用程序在离线状态下也能照常显示已经缓存数据来刷新UI,并重新连接到网络的同时保持内容的更新。
使用CloudKit
,让我们可以使用伟大的Apple爸爸提供的后端API来提高用户体验和缩短开发流程。如果你对本教程有任何问题或意见,欢迎参与讨论!
入门总体就这些了,深入的进阶我也在探索当中,如果你也想出道,阔以买件窃·格瓦拉T恤穿出去看会不会被打死,没死你就成功出道辣。。。