Andy矢倉

老板 来杯董小姐 去冰 少糖 多放点小姐

曾梦想 仗贱走天涯 看尽女人和繁华


滚蛋吧!服务器 · Begining CloudKit

转载请注明,原文地址:滚蛋吧!服务器 · Begining CloudKit

各位早年大概都听说过Parse这家领先的BaaS提供商,它为移动开发提供强有力的后端支持,包括云存储、数据分析、用户关系等等。不过它的命运大概也就是被FB收购之后被家暴中了李阳神功第九重,以至于一年之后暴毙家中。

什么?你没听说过Parse?没听说过BaaS?那xx的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 IDTeam,就如前面说的,必须要有付费的开发者计划才能获取到CloudKit权限。熟悉iOS开发的童鞋对这个过程肯定是So easy。

打开BabiFud.xcodeproj,并在Project Navigator选择BabiFud工程,接着选择BabiFud target,在General选项里替换好Bundle Identifier,最后选着好有付费开发者计划的Team即可。



得益于新版Xcode的优势,Team将自动绑定好Bundle Identifier,现在,你只需要设置好CloudKit并且创建一些containers来保存数据即可。

Entitlements 和 Containers

在添加任何数据之前,我们需要一个Container也就是官方术语中的容器来保存数据记录,也就是RecordContainer在官方术语中是服务器上所有应用程序数据概念位置。目前有三种,PublicPrivateShare

  • 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 IDBundle ID冲突。如,Bundle IDcom.<your domain> .BabiFud,则iCloud里的Container IDiCloud.com.<your domain> .BabiFud。这个工作Xcode会自动帮你完成,务须担心!

  • 由于CloudKit要访问数据的全局标识符,所以Container ID必须唯一(其实Container ID包含Bundle ID,App要上架Bundle ID肯定要唯一,所以也就没什么好说明的了)。
  • 如果能确保自己的Apple ID是在开发者计划的有效期内,只需要在配置项里选好对应的TeamXcode就会自动帮你完成证书配置文件的一系列工作,如果偶尔抽风了,可以强退,也阔以直接编辑info.plist或者BabiFud.entitlements

CloudKit Dashboard

完成必要的设置之后,下一步就要创建记录类型(Rcord Types)和定义数据了。我们可以直接点击CloudKit仪表盘,如图上位置。



你也可以直接访问地址:https://icloud.developer.apple.com/dashboard/

新版的界面就长这样,看起来比老版本丑了不是一个数量级,设计组的人怎么想的,赐予你们随意感受下:



新版CloudKit Dashboard包含:DataLogsTelemetryPublic Database UsageApi 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需要创建一个名为EstablishmentRecord Type数据模型,企业模型需要很多数据构成:名称,位置和可用性,以及各种适合儿童的选项。除了内置字段以外,我们的自定义字段包含:

  • Name:企业名称。
  • Location:企业地理位置信息。
  • CoverPhoto:企业Logo或者封面。
  • ChangingTable:婴儿换衣台类型。
  • SeatingType:座位类型。
  • HealthyOption:健康指数选项。
  • KidsMenu:儿童菜单。

CloudKit Dashboard里进入DevelopmentData分组,选择RECORD TYPE栏,如图所示,选择Create New Type创建新的记录类型:



记住新创建的记录类型名称为:Establishment,千万别弄错了,不然Demo报错找不到对应的记录。

接下来我们添加自定义字段,点击Add Field,新建Name字段,类型属性是String,最后点击Save Record Type保存新建字段。



然后新建索引,切换到INDEXS栏,选中刚刚新建的记录类型Establishment,点击Add IndexName字段添加三个索引,分别是:QUERYABLESOTREABLESEARCHABLE



注意,CloudKit Quick Start官方文档没有及时更新,在老版本里面,新建字段会自动新建索引,新版CloudKit Dashboard则不会,需要我们自己按需添加。

剩下的Field字段Index索引按照下表自行添加:



我们可以一次性点击添加多个字段,你非要浪费生命一个个添加我也没有什么办法来说你。



添加之后基本上就长这样了,如果程序报错,可以回来稍微对照一盘。




一次性添加字段,一定要检查好字段名称和对应的类型属性。如果你弄错了,唯一能改的方法就是删了对应字段,再从新添加。

接下来,就要添加真正的记录了。选中RECORDS栏,确认好Public DatabaseDefault Zone,然后点击Create New Record…按钮,创建真正的记录。



接着我们要做到就是添加一些虚拟数据。至于位置信息建议都稍微集中点,这里我们把定位都设在苹果总部附近,方便在模拟器中查看。请根据下图把模拟数据,或者你兴致好自己想也成。



创建完成之后,怎么知道有没有成功呢?得益于我们之前已经建立好索引,现在我们直接选择好记录类型查询即可。



新版本UI不像老版本,创建成功之后会直接显示记录,而是需要根据自己需求查询。

对于每个记录,数据和App里表示可能完全是不同的,例如,SeatingTypeChangingTablestructs。因此,座椅类型的Int值可能对应于“高脚椅”或“助推器”座椅。对于HealthyOptionKidsMenu,Int值表示布尔类型:0表示建立不具有该选项,1表示它是。

如果你一直在跟着教程走,前期已经把开发者账号准备好,并且工程的证书,配置文件,iCloud报错都搞定之后,我们马上才能开始真正的代码之旅。

如果还有问题,可以对照教程查错或是自行检查。

你要是操作的够丝滑,现在可以打开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)方法的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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顶部:

1
2
3
4
5
6
7
8
9
10
11
12
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顶部实现如下协议:

1
2
3
4
protocol ModelDelegate {
func errorUpdating(error: NSError)
func modelUpdated()
}

接着打开MasterViewController.swift,覆盖掉modelUpdated()的实现:

1
2
3
4
func modelUpdated() {
refreshControl?.endRefreshing()
tableView.reloadData()
}

当有数据回来时,tableView(_:cellForRowAtIndexPath:)已经添加好实现,会自动Cell处理刷新。
同样,覆盖掉errorUpdating(_:)的实现:

1
2
3
4
5
6
7
8
9
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

考虑到懒癌患者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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(_:)方法里的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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恤

最近的文章

Swift4 终极解析方案:基础篇

做过网络开发,特别是互联网,甚至移动端开发的,日常对于数据解析,早年主流的XML,现今主流的JSON都是非常熟悉的,说道解析,系统自带和各种第三方的解析库,除了解析当然也当不了懒癌的脚步,各种model反射库。对于Objective-C各种方案都尤为成熟,甚至还有专门的MacApp用于model生 …

于  Codable, JSON, Swift4 继续阅读
更早的文章

Github仓库迁移,替换Commit数据信息

最近迁移了Github上旧的脑残账号,把仓库迁移到新账号的时候无意中发现Github提供了替换Commit的方法,由于仓库里都是自己提交的代码,索性通过Github提供的脚本把旧账号的信息全部替换到新账号,这样新账号还能装上经常Contribution的逼,保证不侧漏。 官方提供了入口:http …

于  Github, Location, git 继续阅读