ExistAPI
一个在 iOS 上与 Exist API 交互的框架。
ExistAPI 使用 PromiseKit
将 Exist API 封装起来(通过 promise),以便更容易以命令式的方式处理 API 响应并链接 API 调用。它还将所有 API 响应反序列化为强类型的 Decodable
模型。
安装
通过 Cocoapods
pod 'ExistAPI', '~> 0.0.13'
快速示例
从 Exist API 获取令牌以授权您的用户(有关详细信息,请参阅存在 API 文档中的OAuth2 认证)。然后创建 API 实例
let token = getTokenFromAuthProcess()
let existAPI = ExistAPI(token: token)
获取过去一周的用户的属性
existAPI.attributes()
.done { attributes, response in
// handle attribute models here
}.catch { error in
// deal with errors
}
获取带有某些参数的属性
existAPI.attributes(names: ["steps", "mood"], limit: 12)
.done { attributes, response in
// handle data here
}.catch { error in
// handle error here
}
获取属性
existAPI.acquire(names: ["steps"])
.done { attributeResponse, urlResponse in
// deal with data here
}.catch { error in
// handle error
}
更新一些属性的属性值
let steps = IntAttributeUpdate(name: "steps", date: Date(), value: 3158)
let distance = FloatAttributeData(name: "steps_distance", date: Date(), value: 1.2)
existAPI.update(attributes: [steps, distance])
.done { attributeResponse, urlResponse in
// if some attributes failed but some succeeded, check failures here
}.catch { error in
// handle error
}
使用方法
ExistAPI 使用 PromiseKit 处理异步网络任务。如果您以前未使用过 Promise,PromiseKit 文档非常好,可以帮助您了解基本用法。基本上,Promise 允许您对异步任务的结果进行操作,就像它是同步的,使您的代码更容易编写、阅读和维护。
ExistAPI类中可用的每个公共函数都返回一个Promise。您可以将它们一起链接,但最简单的用法是使用.done
闭包来处理结果,以及使用.catch
闭包来处理错误。
请参阅下面的示例,了解如何更有效地使用ExistAPI的Promise。
需求
- Swift 4.0
- iOS 12
- Exist 账户
- Cocoapods
开始使用
首先,您需要创建一个Exist开发者客户端。阅读这篇博客文章可以详细了解如何创建开发者客户端。
为了快速开始,您可以将从开发者客户端详情页复制的个人认证令牌粘贴进来。这将使您能够立即使用Exist API进行测试,并可以将构建用户授权流程的工作留到稍后。
重要说明
- 在使用
Date
与ExistAPI
配合使用时,请始终使用本地设备时间。
创建ExistAPI实例
使用您的令牌创建一个ExistAPI实例,并可选地为网络请求设置特定的超时时间。
let existAPI = ExistAPI(token: yourToken, timeout: 40)
GET请求
func attributes(names: [String]?, limit: Int?, minDate: Date?, maxDate: Date?) -> Promise<(attributes: [Attribute], response: URLResponse)>
返回一个Attribute
模型的数组。
struct Attribute: AttributeValues {
let name: String
let label: String
let group: AttributeGroup
let priority: Int
let service: String
let valueType: ValueType
let valueTypeDescription: String
private let values: [AttributeData]
}
public protocol AttributeValues {
func getIntValues() throws -> [IntValue]
func getStringValues() throws -> [StringValue]
func getFloatValues() throws -> [FloatValue]
}
每个属性都使用 AttributeValues
协议以 Int
,String
或 Float
形式返回其值。您必须根据所处理的属性的值类型选择正确的对应函数。所有可用的属性及其类型可以在 Exist API 文档中找到。
值类型模型如下
public protocol ValueObject {
associatedtype ValueType
var value: ValueType? { get }
var date: Date { get }
}
public struct IntValue: ValueObject {
public typealias ValueType = Int
public var value: Int?
public var date: Date
}
public struct StringValue: ValueObject {
public typealias ValueType = String
public var value: String?
public var date: Date
}
public struct FloatValue: ValueObject {
public typealias ValueType = Float
public var value: Float?
public var date: Date
}
func insights(limit: Int?, pageIndex: Int?, minDate: Date?, maxDate: Date?) -> Promise<(insights: InsightResponse, response: URLResponse)>
返回一个 InsightResponse
struct InsightResponse {
let count: Int
var next: String?
var previous: String?
let results: [Insight]
}
struct Insight: Codable {
let created: Date
let targetDate: Date?
let type: InsightType
let html: String
let text: String
}
func averages(for attribute: String?, limit: Int?, pageIndex: Int?, minDate: Date?, maxDate: Date?) -> Promise<(averages: [Average], response: URLResponse)>
返回一个 Average
模型数组
class Average: Codable {
let attribute: String
let date: Date
let overall: Float
let monday: Float
let tuesday: Float
let wednesday: Float
let thursday: Float
let friday: Float
let saturday: Float
let sunday: Float
}
func correlations(for attribute: String?, limit: Int?, pageIndex: Int?, minDate: Date?, maxDate: Date?, latest: Bool?) -> Promise<(correlations: [Correlation], response: URLResponse)>
返回一个 Correlation
模型数组
class Correlation: Codable {
let date: Date
let period: Int
let attribute: String
let attribute2: String
let value: Float
let p: Float
let percentage: Float
let stars: Int
let secondPerson: String
let secondPersonElements: [String]
let strengthDescription: String
let starsDescription: String
let description: String?
let occurrence: String?
let rating: CorrelationRating?
}
func user() -> Promise<(user: User, response: URLResponse)>
返回一个 User
模型
class User: Codable {
let id: Int
let username: String
let firstName: String
let lastName: String
let bio: String
let url: String
let avatar: String
let timezone: String
let imperialUnits: Bool
let imperialDistance: Bool
let imperialWeight: Bool
let imperialEnergy: Bool
let imperialLiquid: Bool
let imperialTemperature: Bool
let trial: Bool
let delinquent: Bool
}
POST 请求
属性必须在服务中专属之后才能被更新。要拥有一个属性,请使用 acquire
函数。尝试更新不属于您的客户端的属性将会失败。请参阅 Exist API 文档中的属性专属说明。
public func acquire(names: [String]) -> Promise<(attributeResponse: AttributeResponse, response: URLResponse)>
返回一个 AttributeResponse
public struct AttributeResponse: Codable {
var success: [SuccessfulAttribute]?
var failed: [FailedAttribute]?
}
public struct SuccessfulAttribute: Codable {
var name: String
var active: Bool?
}
public struct FailedAttribute: Codable {
var name: String
var errorCode: String
var error: String
}
要停止拥有一个属性,请使用 release
函数
public func release(names: [String]) -> Promise<(attributeResponse: AttributeResponse, response: URLResponse)>
在调用 update
函数之前,请确保您已经阅读了 关于属性获取和更新的 Exist API 文档。
public func update<T: AttributeUpdate>(attributes: [T]) -> Promise<(attributeResponse: AttributeUpdateResponse, response: URLResponse)>
接受符合 AttributeUpdate
协议的对象数组
public protocol AttributeUpdate: Codable {
associatedtype Value: Codable
var name: String { get }
var date: Date { get }
var value: Value { get }
func dictionaryRepresentation() throws -> [String: Any]?
}
存在三种 AttributeUpdate
的具体实现
public struct StringAttributeUpdate: AttributeUpdate {
public typealias Value = String
}
public struct FloatAttributeUpdate: AttributeUpdate {
public typealias Value = Float
}
public struct IntAttributeUpdate: AttributeUpdate {
public typealias Value = Int
}
update
函数返回一个 AttributeUpdateResponse
public struct AttributeUpdateResponse: Codable {
var success: [SuccessfullyUpdatedAttribute]?
var failed: [FailedToUpdateAttribute]?
}
public struct SuccessfullyUpdatedAttribute: Codable {
var name: String
var date: Date
var value: String?
}
public struct FailedToUpdateAttribute: Codable {
var name: String?
var date: Date?
var value: String?
var errorCode: String
var error: String
}
链式操作
由于 Exist API 使用 PromiseKit,您可以连续调用多个方法。以下是一些示例
由于我们需要先获取用户 Exist 账户中的属性,然后才能更新这些属性,因此第一次尝试使用数据更新属性时,我们可以将这些步骤链在一起
existAPI.acquire(names: ["weight"]
.then { attributeResponse, urlResponse in
guard let success = attributeResponse.success,
success.contains("weight") else { return }
let update = FloatAttributeUpdate(name: "weight", date: Date(), value: 65.8)
existAPI.update(attributes: updates)
.done { attributeUpdateResponse, urlResponse in
// handle success
}.catch { error in
// handle error
}
PromiseKit 允许我们使用 when
来在所有承诺都被完成时才执行某个动作,并在这个承诺链中只处理一次错误。使用 when
可以一次性调用 Exist API 的一组方法并处理所有返回的数据
let insightsPromise = existAPI.insights(days: 30)
let averagesPromise = existAPI.averages(limit: 30)
let correlationsPromise = existAPI.correlations
when(fulfilled: [insightsPromise, averagesPromise, correlationsPromise])
.done { insights, averages, correlations in
// handle data
}.catch { error in
// handle errors just once for all these promises
}
构建应用程序
使用 Xcode 10 克隆源代码并构建。
运行测试
在ExistAPITests
内部创建一个名为TestConstants.swift
的新文件,并添加一个名为TEST_TOKEN
的字符串常量,如下所示
let TEST_TOKEN = "your_token_string_here"
没有这个,测试将失败。您可以通过在Exist账户中创建开发者客户端来获取访问令牌。
待办
- GET请求
- POST请求
- 创建一个便捷的
func
来访问只有今天的属性 - 支持附加自定义标签
- 为包括请求中的查询添加测试