移动周分享-第61期

CloudKit初探 - 刘康

什么是CloudKit

  • iCloud
  • Documents
  • iCloud Drive
  • CloudKit

CloudKit 基础对象类型

  • CKContainer: Containers 就像应用运行的沙盒一样,一个应用只能访问自己沙盒中的内容而不能访问其他应用的。Containers 就是最外层容器,每个应用有且仅有一个属于自己的 container。

  • CKDatabase: Database 即数据库,私有数据库用来存储敏感信息,比如说用户的性别年龄等,用户只能访问自己的私有数据库。应用也有一个公开的数据库来存储公共信息,例如你在构建一个基于地理位置的应用,那么地理位置信息就应该存储在公共数据库里以便所有用户都能访问到。

  • CKRecord: 即数据库中的一条数据记录。CloudKit 使用 record 通过 key-value 结构来存储结构化数据。关于键值存储,目前值的架构支持 NSString、NSNumber、NSData、NSDate、CLLocation,和 CKReference、CKAsset(这两个下面我们会说明),以及存储以上数据类型的数组。

  • CKRecordZone: Record 不是以零散的方式存在于 database 之中的,它们位于 record zones 里。每个应用都有一个 default record zone,你也可以有自定义的 record zone。

  • CKRecordIdentifier: 是一条 record 的唯一标识,用于确定该 record 在数据库中的唯一位置。

  • CKReference: Reference 一种引用关系。以地理位置签到应用为例,每个地理位置可以包含很多用户在该位置的签到,那么位置与签到之间就形成了这样一种包含式的从属关系。

  • CKAsset: 即资源文件,例如二进制文件。还是以签到应用为例,用户签到时可能还包含一张照片,那么这张照片就会以 asset 形式存储起来。

有哪些Api(Diary Demo)

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 updateRecord(diary: Diary, record: CKRecord) {

record.setObject(diary.content, forKey: "Content")

record.setObject(diary.created_at, forKey: "Created_at")

if let location = diary.location {
record.setObject(location, forKey: "Location")
}

if let title = diary.title {
record.setObject(title, forKey: "Title")
}

record.setObject(diary.id, forKey: "id")

privateDB.saveRecord(record, completionHandler: { (newDiary, error) -> Void in

debugPrint("Diary Updated")

if let error = error {
debugPrint("error \(error.description)")
}

})
}

1
2
3
4
5
func deleteCloudRecord(record: CKRecord) {
privateDB.deleteRecordWithID(record.recordID) { (recordID, error) -> Void in
print("Delete \(recordID?.recordName) \(error?.description)")
}
}

查询 -> 修改 -> 更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func fetchCloudRecordWithTitle(title: String , complete: ([CKRecord]?) -> Void) {

let predicate = NSPredicate(format: "Title == %@", title)

let query = CKQuery(recordType: "Diary",
predicate: predicate )

privateDB.performQuery(query, inZoneWithID: nil) {
results, error in
if let results = results {
complete(results)
} else {
complete(nil)
}
}
}

碰到的问题(踩到的坑)

  1. Saving CloudKit Record “Not Authenticated” (9/1002)“ ”This request requires an authenticated account
    原因:使用模拟器或者设备没有启用iCloud
  2. Couldn't get container configuration from the server for container
    原因:container’s identifier错误,可能是使用默认值,也可能是与代码里初始化container时的identifier不一致;
    另外,需要与开发者中心Certificates, Identifiers & Profiles的iCloud Containers保持一致。需要5分钟左右才可以使用
  3. Invalid Arguments(12/2015); server message = Field '___recordID' is not marked queryable
    原因:未设置Metadata Indexes.需要前往CloudKit Dashboard -> Record Types -> Metedata Indexes设置

收费

等不及要试试 CloudKit 了?它能让你从编写服务端代码、监控服务器压力、长期维护大量的 CDN、租用服务器等等等等的事情中解脱出来。等等!CloudKit 怎么收费呢,会很贵吗?答案是:免费。目前苹果允许你使用 CloudKit 存储 10 GB 资源,100 M 数据库存储,每天 2 GB 流量;当你的用户数量增加的时候,这些免费额度也相应地增加到 1 PB 存储、10 TB 数据库存储,以及每天 200 TB 流量。

参考

https://developer.apple.com/library/ios/documentation/DataManagement/Conceptual/CloudKitQuickStart/EnablingiCloudandConfiguringCloudKit/EnablingiCloudandConfiguringCloudKit.html#//apple_ref/doc/uid/TP40014987-CH2-SW1

https://icloud.developer.apple.com/dashboard

http://nshipster.cn/cloudkit/

@(iOS)[universal links, 通用链接]

  • 通过唯一的网址, 就可以链接一个特定的视图到你的 APP 里面, 不需要特别的 schema
  • 不再需要JavaScript检查平台,跳转也不需要js去做特定处理
  • 比scheme更灵活更安全的匹配URL跳转

注意:不能使用模拟器调试

image

工作原理:When the app is installed, the system downloads and verifies the site association file for each of its associated domains. If the verification is successful, the app is associated with the domain.

Configure your file server

  • 我们的根路径配置文件
  • 根路径配置有效性验证
    要求如下:

    Alright! So you have your signed apple-app-site-association file. Now you just need to configure your file server to host this for you. There are a few caveats:

  • It must be sent with the header ‘application/pkcs7-mime’
  • It must be sent from the endpoint youdomain.com/apple-app-site-association
  • It must return a 200 http code.

We set up the one for all Branch integrated apps using our Node+Express link servers. Here’s the code we used in case that’s helpful:

1
2
3
4
5
var aasa = fs.readFileSync(__dirname + '/static/apple-app-site-association');
app.get('/apple-app-site-association', function(req, res, next) {
res.set('Content-Type', 'application/pkcs7-mime');
res.status(200).send(aasa);
});

可以从原有的scheme过渡过来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
[self handleRouting:url];
return YES;
}

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *))restorationHandler {
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
[self handleRouting:userActivity.webpageURL];
}
return YES;
}

- (void)handleRouting:(NSURL *)url {
....
}

视频优先推荐
https://developer.apple.com/videos/play/wwdc2015/509/

优秀博客
https://blog.branch.io/how-to-setup-universal-links-to-deep-link-on-apple-ios-9

https://blog.branch.io/ios-9.2-deep-linking-guide-transitioning-to-universal-links

http://blog.hokolinks.com/how-to-implement-apple-universal-links-on-ios-9/

可能出现的bug(巨坑,也可能是配置问题)
http://stackoverflow.com/questions/32751225/ios9-universal-links-does-not-work

结合实例介绍 nginx 配置 - 曾铭

默认配置

DNS, nginx, tomcat, application

https://api-pre.51offer.com/mobile/user/login

DNS: api-pre.51offer.com(host) -> IP(nginx)

nginx: IP(nginx):80 -> IP(tomcat):8080

tomcat: /mobile

application: /user/login (controller, function)

看 nginx 配置

两个问题

https://api-pre.51offer.com/xyz.html
https://api-pre.51offer.com/apple-site-app-config

线上 fixbug

http://www.51offer.com/aboutus/server.html?in_app=1

api.51offer.com

https://api.51offer.com/aboutus/server.html?in_app=1
https://api.51offer.com/aboutus/protocol.html?in_app=1