JsonAPI
JsonAPI 是一个符合 Swift JSON:API 标准的实现。
它受到了另一库的极大启发:[Vox](https://github.com/aronbalog/Vox)。
这个库允许多种类型的用法,从框架-style 到“原始”JSON:API 对象操作。
要求
- Xcode 11.x
- Swift 5.x
安装
JsonAPI 不包含任何外部依赖。
以下是目前支持的安装选项
Carthage
要使用 Carthage 将 JsonAPI 集成到您的 Xcode 项目中,请将其指定在您的 Cartfile
中。
github "Aveine/JsonAPI" ~> 1.0
有关使用和安装说明,请访问他们的网站。
CocoaPods
要使用 CocoaPods 将 JsonAPI 集成到您的 Xcode 项目中,请在您的 Podfile
中指定它
pod 'JsonAPISwift', '~> 1.0'
有关使用和安装说明,请访问他们的网站。
Swift Package Manager
要使用 Swift Package Manager 将 JsonAPI 集成到您的 Xcode 项目中,请在您的 Package.swift
中指定它
dependencies: [
.package(url: "https://github.com/aveine/JsonAPI.git", .upToNextMajor(from: "1.0.2"))
]
有关使用和安装说明,请访问他们的网站。
使用
基本
定义资源
import JsonAPI
class Article: Resource {
/*--------------- Attributes ---------------*/
var title: String?
var descriptionText: String?
var keywords: [String]?
var viewsCount: Int?
var isFeatured: Bool?
var customObject: [String: Any]?
/*----------- Nested Attributes ------------*/
struct Tag: ResourceNestedAttribute {
var name: String?
var meta: [String: Any]?
/*---------- Custom mechanics ----------*/
/**
Override the keys expected in the JSON API resource object's attributes to match the nested object's attributes
Format => [resourceObjectAttributeKey: nestedObjectKey]
*/
override class var nestedAttributesKeys: [String : String] {
return [
"tagName": "name"
]
}
/**
Attributes that won't be serialized when serializing to a JSON API resource object
*/
override class var nestedExcludedAttributes: [String] {
return [
"meta"
]
}
}
var mainTag: Tag?
var tags: [Tag]?
/*------------- Relationships -------------*/
var authors: [Person]?
var editor: Person?
/*------------- Resource type -------------*/
// resource type should be defined, otherwise it is the class name
override class var resourceType: String {
return "articles"
}
/*------------- Custom mechanics -------------*/
/**
Override the keys expected in the JSON API resource object's attributes to match the model's attributes
Format => [resourceObjectAttributeKey: modelKey]
*/
override class var resourceAttributesKeys: [String : String] {
return [
"description": "descriptionText"
]
}
/**
Attributes that won't be serialized when serializing to a JSON API resource object
*/
override class var resourceExcludedAttributes: [String] {
return [
"customObject"
]
}
}
网络
客户端
创建一个客户端,用于与您的JSON:API服务器进行通信,并继承以下协议 Client
public typealias ClientSuccessBlock = (_ response: HTTPURLResponse?, _ data: Data?) -> Void
public typealias ClientFailureBlock = (_ error: Error?, _ data: Data?) -> Void
/**
A client using the library
*/
public protocol Client: class {
/**
- Parameter path: Path on which the client must execute the request
- Parameter method: HTTP method the client must use to execute the request
- Parameter queryItems: Potential query items the client must send along with the request
- Parameter body: Potential body the client must send along with the request
- Parameter success: Success block that will be called if the request successed
- Parameter failure: Failure block that will be called if the request failed
- Parameter userInfo: Potential meta information that the user can provide to the client
*/
func executeRequest(path: String, method: HttpMethod, queryItems: [URLQueryItem]?, body: Document.JsonObject?, success: @escaping ClientSuccessBlock, failure: @escaping ClientFailureBlock, userInfo: [String: Any]?)
}
在 executeRequest
方法中,使用您喜欢的任何网络库。
例如,使用Alamofire,您可以创建如下客户端:
import JsonAPI
import Alamofire
public class AlamofireClient: Client {
let baseUrl: URL
public init() {
self.baseUrl = URL(string: "https://api.com")!
}
func getHeaders() -> HTTPHeaders {
return [
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json"
]
}
public func executeRequest(path: String, method: HttpMethod, queryItems: [URLQueryItem]?, body: Document.JsonObject?, success: @escaping ClientSuccessBlock, failure: @escaping ClientFailureBlock, userInfo: [String: Any]?) {
var urlComponents = URLComponents.init(url: self.baseUrl.appendingPathComponent(path), resolvingAgainstBaseURL: false)!
urlComponents.queryItems = queryItems
let url = try! urlComponents.asURL()
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
self.getHeaders().forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
if let body = body {
request.httpBody = try! JSONSerialization.data(withJSONObject: body)
}
SessionManager.default
.request(request)
.validate(statusCode: 200..<300)
.validate(contentType: ["application/vnd.api+json"])
.responseData { (dataResponse) in
let response = dataResponse.response
let data = dataResponse.data
let error = dataResponse.error
dataResponse
.result
.ifSuccess { success(response, data) }
.ifFailure { failure(error, data) }
}
}
}
数据源
与您的资源交互最常见的方式是定义一个数据源。
import JsonAPI
let client = AlamofireClient()
let dataSourceRouter = DataSource<Article>(client: client, strategy: .router(ScrudRouter()))
let dataSourcePath = DataSource<Article>(client: client, strategy: .path("/<type>/<id>"))
数据源需要指定一个 strategy
来了解如何与您的资源交互。这个策略可以是路由器或路径。
对于 path
策略,可以使用 <id>
和 <type>
注释。如果可能,它们将用适当的值替换。
路由器
路由器允许您定义用于交互资源的路径。
例如,这是库中 ScrudRouter
的实现:
/**
Router for classical SCRUD paths architecture
- Search: resourceType
- Create: resourceType
- Read: resourceType/id
- Update: resourceType/id
- Delete: resourceType/id
*/
public class ScrudRouter: Router {
public init() {}
public func search(type: String) -> String {
return type
}
public func create(resource: Resource) -> String {
return resource.type
}
public func read(type: String, id: String) -> String {
return "\(type)/\(id)"
}
public func update(resource: Resource) -> String {
return "\(resource.type)/\(resource.id ?? "")"
}
public func delete(type: String, id: String) -> String {
return "\(type)/\(id)"
}
public func delete(resource: Resource) -> String {
return "\(resource.type)/\(resource.id ?? "")"
}
}
请求
搜索资源
import JsonAPI
let client = AlamofireClient()
let dataSource = DataSource<Article>(client: client, strategy: .router(ScrudRouter()))
dataSource
.search()
.result({ (articles: [Article], document: Document) in
}, { (error: Error?, document: Document?) in
})
读取资源
import JsonAPI
let client = AlamofireClient()
let dataSource = DataSource<Article>(client: client, strategy: .router(ScrudRouter()))
dataSource
.read(id: "1")
.result({ (article: Article?, document: Document?) in
}, { (error: Error?, document: Document?) in
})
创建资源
import JsonAPI
let client = AlamofireClient()
let dataSource = DataSource<Article>(client: client, strategy: .router(ScrudRouter()))
let article = Article()
article.id = "1"
article.title = "Title"
dataSource
.create(article)
.result({ (article: Article?, document: Document?) in
}, { (error: Error?, document: Document?) in
})
更新资源
import JsonAPI
let client = AlamofireClient()
let dataSource = DataSource<Article>(client: client, strategy: .router(ScrudRouter()))
let article = Article()
article.id = "1"
article.title = "Another Title"
dataSource
.update(article)
.result({ (article: Article?, document: Document?) in
}, { (error: Error?, document: Document?) in
})
删除资源
import JsonAPI
let client = AlamofireClient()
let dataSource = DataSource<Article>(client: client, strategy: .router(ScrudRouter()))
let article = Article()
article.id = "1"
dataSource
.delete(article)
.result({ (article: Article?, document: Document?) in
}, { (error: Error?, document: Document?) in
})
或者如果您之前没有获取过该资源
import JsonAPI
let client = AlamofireClient()
let dataSource = DataSource<Article>(client: client, strategy: .router(ScrudRouter()))
dataSource
.delete(id: "1")
.result({ (article: Article?, document: Document?) in
}, { (error: Error?, document: Document?) in
})
插件
实现分页和筛选辅助函数
由于分页和筛选可能因服务器实现而异,下面举例说明如何扩展库以支持它们。
extension ResourceCollectionRequest {
/**
Append the given filters to the request's query items
- Parameter filters: Filters to append
- Returns: The request with the filters query appended
*/
public func filters( _ filters: [ String: [ String ] ] ) -> Self {
var queryItems: [ URLQueryItem ] = []
for filter in filters {
queryItems.append( URLQueryItem( name: "filter[\(filter.key)]", value: filter.value.joined( separator: "," ) ) )
}
return self.queryItems( queryItems )
}
/**
Append the given filters to the request's query items
- Parameter key: Key of filter to append
- Parameter value: Value(s) of filter to append
- Returns: The request with the filters query appended
*/
public func filter( _ key: String, _ value: String...) -> Self {
return self.filters( [ key: value ] )
}
/**
Append the given paging parameters to the request's query items
- Parameter size: Maximum number of items to return
- Parameter number: Page number to return
- Returns: The request with the page query appended
*/
public func page( _ size: Int, number: Int? = nil ) -> Self {
var queryItems: [ URLQueryItem ] = [
URLQueryItem( name: "page[size]", value: String( size ) )
]
if let number = number {
queryItems.append( URLQueryItem( name: "page[number]", value: String( number ) ) )
}
return self.queryItems( queryItems )
}
}
高级
!! 正在准备的章节 !!
API文档
完整的API文档可以在这里找到。
无数据源创建请求
import JsonAPI
let client = AlamofireClient()
let resourceRequest = ResourceRequest<Article>(path: "/articles/1", method: HttpMethod.get, client: client, resource: nil)
let resourceCollectionRequest = ResourceCollectionRequest<Article>(path: "/articles", method: HttpMethod.get, client: client, resource: nil)
resourceRequest
.result({ (article: Article?, document: Document?) in
}, { (error: Error?, document: Document?) in
})
resourceCollectionRequest
.result({ (articles: [Article], document: Document) in
}, { (error: Error?, document: Document?) in
})
原始 JSON:API 对象操作
即将推出