滚蛋吧!服务器 · Begining CloudKit
转载请注明,原文地址:滚蛋吧!服务器 · Begining CloudKit
各位早年大概都听说过Parse这家领先的BaaS提供商,它为移动开发提供强有力的后端支持,包括云存储、数据分析、用户关系等等。不过它的命运大概也就是被FB收购之后被家暴中了李阳神功第九重,以至于一年之后暴毙家中。
什么?你没听说过Parse?没听说过BaaS?那LeanCloud呢?都不知道?好吧,不送了您呢。
今天的猪脚CloudKit,作为Apple在iOS8上推出的基于iCloud的一个云端数据存储服务,提供了低成本的云存储并能作为一个后端服务,通过用户们的iCloud账号分享其应用数据。以上是行话,CloudKit对于移动开发者好处请自行QA or Here。
至于为啥三年后的今天才蹭这个热点,还是归咎于今年正式出道,成了流落街头的个人开发够。
我能怎么办?我也很绝望啊!!!
你要问我原因???
iOS没人要辣
又或者。。。
打工这方面 打工是不可能打工的 这辈子不可能打工的
本文你将学习到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.<your app's bundle id>
的容器,如图所示:
iCloud问题排查
如果在创建,构建项目或运行应用程序时看到任何警告或错误,或者Xcode正在吐槽Container ID
有问题什么的,可以根据以下提示进行故障排除:
-
如果上述步骤操作
iCloud Kit
显示任何警告或错误,请尝试Fix Issue
按钮,Xcode并没有你想象中的那么人工智能,或者需要修复几次。
-
为避免
Container ID
和Bundle ID
冲突。如,Bundle ID
为com.<your domain> .BabiFud
,则iCloud里的Container ID
为iCloud.com.<your domain> .BabiFud
。这个工作Xcode会自动帮你完成,务须担心! -
由于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恤入门总体就这些了,深入的进阶我也在探索当中,如果你也想出道,阔以买件窃·格瓦拉T恤穿出去看会不会被打死,没死你就成功出道辣。。。