JsonAPISwift 1.3.0

JsonAPISwift 1.3.0

Christopher Paccard 维护。



  • Christopher Paccard

JsonAPI

platforms JsonAPI CI Carthage compatible pod

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 对象操作

即将推出