滚蛋吧!服务器 · 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 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)方法的内容:

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恤
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容