RxRetroSwift

CI Status Version License Platform

它能做什么?

它简化了你的 RESTful API 调用,使用苹果最新的 Codable 功能自动将 HttpResponse 转换为指定的 Model 以及 Error。

例如,在请求获取特定用户信息并且你有一个 User 模型时,你所要做的就是让 User 模型遵循 Codable 协议,并在使用 RequestCaller 时指定它。

{
  "name":"kel",
  "email":"me@iamkel.net"
}

User 模型遵循 Codable 协议。

struct User: Codable {
  var name:String
  var email:String
}

这将自动将响应转换为 User 模型的一个实例。

示例

let caller = RequestCaller(config: URLSessionConfiguration.default)

func fetchUser(byUserId userId) -> Observable<Result<User, ErrorModel>> {
    let request:URLRequest = RequestModel(
      httpMethod: .get,
      path: "v1/users/\(userId)")
      .asURLRequest()
    return caller.call(request)
  }

假设这是一个用户数组;由于 Array 遵循 Codable 协议,你所要做的就是将类型指定为 [User]

示例

func fetchUsers() -> Observable<Result<[User], ErrorModel>> {
    let request:URLRequest = RequestModel(
      httpMethod: .get,
      path: "v1/users")
      .asURLRequest()
    return caller.call(request)
  }

关于处理 ResponseError

RxRetroSwift 提供了一个类型别名 ErrorCodable,它是 HasErrorInfoDecodable 协议的组合。

public typealias DecodableError = Decodable & HasErrorInfo

例如,你的登录请求的 json 错误响应是:

{
  "message": "Unable to login."
  "details": {
    "password": "You changed your password 2 months ago."
  }
}

你有以下模型:

struct ErrorModel {

  var errorCode:Int?
  var message:String
  var details:[String:String]
}

如何处理不期望返回对象或模型的请求?

RxRetroSwift 提供了一个方法,它将返回 Observable<Result<RawResponse>, DecodableErrorModel>>。

public func call<DecodableErrorModel:DecodableError>(_ request: URLRequest)
  -> Observable<Result<RawResponse, DecodableErrorModel>>
public struct RawResponse {
  
  public var statusCode:Int
  public var data:Data?
  
  init(statusCode:Int, data:Data?) {
    self.statusCode = statusCode
    self.data       = data
  }
}

示例

要运行示例项目,请克隆 repo,然后首先从 Example 目录运行 pod install

要求

特性

待办事项

安装

Cocoapods

RxRetroSwift 现在可以通过 CocoaPods 获得。 要安装它,只需将以下行添加到您的 Podfile 中

pod 'RxRetroSwift'

Swift Package Manager

.package(url: "https://github.com/michaelhenry/RxRetroSwift", from: "2.1"),

示例实现

使用 JSONPlaceholder API。 您还可以查看 示例项目

class APIClient {
 
  static var shared = APIClient()
  var caller = RequestCaller.shared
  
  private init() {
    
    RequestModel.defaults.baseUrl = "https://jsonplaceholder.typicode.com"
  }
  
  func fetchPosts() -> Observable<Result<[Post], ErrorModel>> {
    let request = RequestModel(
      httpMethod: .get,
      path: "posts")
      .asURLRequest()
    
    return caller.call(request)
  }
  
  func insertPost(post:Post) -> Observable<Result<Post, ErrorModel>> {
    let request = RequestModel(
      httpMethod: .post,
      path: "posts",
      payload: post.dictionaryValue)
      .asURLRequest()
    
    return caller.call(request)
  }

  func fetchComments() -> Observable<Result<[Comment], ErrorModel>> {
    let request = RequestModel(
      httpMethod: .get,
      path: "comments")
      .asURLRequest()
    
    return caller.call(request)
  }
  
  func fetchAlbums() -> Observable<Result<[Album], ErrorModel>> {
    let request = RequestModel(
      httpMethod: .get,
      path: "albums")
      .asURLRequest()
    
    return caller.call(request)
  }
  
  func fetchPhotos() -> Observable<Result<[Photo], ErrorModel>> {
    let request = RequestModel(
      httpMethod: .get,
      path: "photos")
      .asURLRequest()
    
    return caller.call(request)
  }
  
  func fetchTodos() -> Observable<Result<[Todo], ErrorModel>> {
    let request = RequestModel(
      httpMethod: .get,
      path: "todos")
      .asURLRequest()
    
    return caller.call(request)
  }
  
  func fetchUsers() -> Observable<Result<[User],ErrorModel>> {
    
    let request = RequestModel(
      httpMethod: .get,
      path: "users")
      .asURLRequest()

    return caller.call(request)
  }
}

测试

class TestAPIClient:QuickSpec {
  
  override func spec() {
    
    describe("Using JSONPlaceholder API") {
      
      let apiClient = APIClient.shared
      
      it("Check Posts result count"){
        let observable = apiClient.fetchPosts()
        expect(observable.map { $0.value!.count }).first == 100
      }
      
      it("Can insert post"){
        var post = Post()
        let title = "This is my post"
        let userId = 101
        let body = "This is a message body"
        
        post.title = title
        post.userId = userId
        post.body = body
        let observable = apiClient.insertPost(post: post)
        expect(observable.map { $0.value?.title ?? "" }).first == title
        expect(observable.map { $0.value?.userId ?? 0 }).first == userId
        expect(observable.map { $0.value?.body ?? "" }).first == body
      }
      
      it("Check Comments result count"){
        let observable = apiClient.fetchComments()
        expect(observable.map { $0.value!.count }).first == 500
      }
      
      it("Check Albums result count"){
        let observable = apiClient.fetchAlbums()
        expect(observable.map { $0.value!.count }).first == 100
      }
      
      it("Check Photos result count"){
        let observable = apiClient.fetchPhotos()
        expect(observable.map { $0.value!.count }).first == 5000
      }
      
      it("Check Todos result count"){
        let observable = apiClient.fetchTodos()
        expect(observable.map { $0.value!.count }).first == 200
      }
      
      it("Check Users result count"){
        let observable = apiClient.fetchUsers()
        expect(observable.map { $0.value!.count }).first == 10
      }
    }
  }
}

贡献

欢迎随意提交 pull request 或提出任何有用的建议。

作者

Michael Henry Pantaleon, me@iamkel.net

许可证

RxRetroSwift 在 MIT 许可下可用。 有关更多信息,请参见 LICENSE 文件。