CI codecov.io CocoaPod platform CocoaPod version Swift Package Manager compatible Packagist

TRON 是一个轻量级的网络抽象层,构建于 Alamofire 之上。 它可以被用来极大地简化与 RESTful JSON Web 服务的交互。

特性

概述

我们设计 TRON 的目标是简单易用,并且非常容易定制。 完成初始设置后,使用 TRON 非常直接。

let request: APIRequest<User,APIError> = tron.codable.request("me")
request.perform(withSuccess: { user in
  print("Received User: \(user)")
}, failure: { error in
  print("User request failed, parsed error: \(error)")
})

要求

安装

Swift Package Manager

TRON 框架包含 Codable 实现。 要使用 SwiftyJSON,请 import TRONSwiftyJSON 框架。 要使用 RxSwift 包装器,请 import RxTRON

CocoaPods

pod 'TRON', '~> 5.3.0'

仅核心子规范,没有 SwiftyJSON 依赖

pod 'TRON/Core', '~> 5.3.0'

TRON 的 RxSwift 扩展

pod 'TRON/RxSwift', '~> 5.3.0'

迁移指南

项目状态

TRON 由 MLSDev Inc. 积极开发中。 欢迎提交 Pull Request!

请求构建

TRON 对象用作 APIRequest 的初始配置器,设置所有基本值并配置为与 baseURL 一起使用。

let tron = TRON(baseURL: "https://api.myapp.com/")

您需要保持对 TRON 对象的强引用,因为它持有 Alamofire.Manager,Alamofire.Manager 正在运行所有请求。

URLBuildable

URLBuildable 协议用于将相对路径转换为 URL,该 URL 将被请求使用。

public protocol URLBuildable {
    func url(forPath path: String) -> URL
}

默认情况下,TRON 使用 URLBuilder 类,该类只是将相对路径附加到基本 URL,这在大多数情况下都足够了。 您可以通过更改 TRON 上的 urlBuilder 属性来全局自定义 URL 构建过程,或者通过修改 APIRequest 上的 urlBuilder 属性来本地自定义单个请求。

发送请求

要发送 APIRequest,请在 APIRequest 上调用 perform(withSuccess:failure:) 方法

let alamofireRequest = request.perform(withSuccess: { result in }, failure: { error in})

或者,您可以使用 performCollectingTimeline(withCompletion:) 方法,该方法在完成闭包中包含 Alamofire.Response

request.performCollectingTimeline(withCompletion: { response in
    print(response.timeline)
    print(response.result)
})

在这两种情况下,如果需要,您都可以额外链接 Alamofire.Request 方法

request.perform(withSuccess: { result in }, failure: { error in }).progress { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
    print(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
}

响应解析

通用的 APIRequest 实现允许我们在甚至发送请求之前定义预期的响应类型。 在 Alamofire DataResponseSerializerProtocol 之上,我们为错误处理添加了一个额外的协议。

public protocol DataResponseSerializerProtocol {
    associatedtype SerializedObject

    public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Self.SerializedObject
}

public protocol ErrorSerializable: Error {
    init?(serializedObject: Any?, request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?)
}

Codable

使用 Swift4 Codable 协议解析模型很简单,实现 Codable 协议

struct User: Codable {
  let name : String
  let id: Int
}

然后发送请求

let request: APIRequest<User,APIError> = tron.codable.request("me")
request.perform(withSuccess: { user in
  print("Received user: \(user.name) with id: \(user.id)")
})

可以自定义模型和错误解析的解码器

let userDecoder = JSONDecoder()

let request : APIRequest<User,APIError> = tron.codable(modelDecoder: userDecoder).request("me")

JSONDecodable

TRON 提供了 JSONDecodable 协议,允许我们使用 SwiftyJSON 解析模型

public protocol JSONDecodable {
    init(json: JSON) throws
}

要使用 SwiftyJSON 解析来自服务器的响应,您所需要做的就是创建一个符合 JSONDecodable 类型的类型,例如

class User: JSONDecodable {
  let name : String
  let id: Int

  required init(json: JSON) {
    name = json["name"].stringValue
    id = json["id"].intValue
  }
}

然后发送请求

let request: APIRequest<User,MyAppError> = tron.swiftyJSON.request("me")
request.perform(withSuccess: { user in
  print("Received user: \(user.name) with id: \(user.id)")
})

对于 Swift 内置类型(如 String、Int、Float、Double 和 Bool),也存在 JSONDecodable 协议的默认实现,因此您可以轻松地执行以下操作

let request : APIRequest<String,APIError> = tron.swiftyJSON.request("status")
request.perform(withSuccess: { status in
    print("Server status: \(status)") //
})

在您不关心实际响应的情况下,您还可以使用 Alamofire.Empty 结构体。

一些关于响应序列化的概念,包括数组响应序列化器,在 容器类型解析文档 中进行了描述

可以自定义 JSONSerialization.ReadingOptionsSwiftyJSON.JSON 对象在解析响应数据时会使用这些选项

let request : APIRequest<String, APIError> = tron.swiftyJSON(readingOptions: .allowFragments).request("status")

Swift 并发

使用 Swift 并发发送请求是通过代理对象 RequestSender(或下载请求的 DownloadRequestSender)完成的。 简单用法示例

let request : APIRequest<User, APIError> = tron.codable.request("/me")
do {
 let user = try await request.sender().value
  // user variable contains User type
} catch {
  // Network request failed
}

如果您更喜欢接收包含成功模型或错误模型的结果,您也可以这样做

let request : APIRequest<User, APIError> = tron.codable.request("/me")
let result = await request.sender().result
// result is Result<User,APIError>

如果需要,还有一个包含所有请求信息的 response 异步属性

let request : APIRequest<User, APIError> = tron.codable.request("/me")
let response = await request.sender().response
// response: AFDataResponse<Model>

上传请求

对于上传请求,监控上传进度并将其显示给用户非常有用

let request : APIRequest<User, APIError> = tron.codable.request("/me/profile_picture")
  .upload("/post", fromFileAt: urlForResource("cat", withExtension: "jpg"))
  .method(.post)  

let sender = request.sender()
Task {
    for await progress in sender.uploadProgress {
      // Update progress view, progress: Progress
    }
}
let result = await sender.result

下载请求

与上传请求类似,下载请求具有作为异步序列实现的 downloadProgress 属性

Task {
    for await progress in sender.downloadProgress {
      // Update download view, progress: Progress
    }
}

如果您只关心下载的文件 URL,而不关心解析的数据模型,您可以等待请求发送器上的 responseURL 属性

let destination = Alamofire.DownloadRequest.suggestedDownloadDestination()
let request: DownloadAPIRequest<URL, APIError> = tron
            .download("/download",
                      to: destination,
                      responseSerializer: FileURLPassthroughResponseSerializer())
do {
  let fileURL = try await request.sender().responseURL 
} catch {
  // Handle error
}

RxSwift

let request : APIRequest<Foo, APIError> = tron.codable.request("foo")
_ = request.rxResult().subscribe(onNext: { result in
    print(result)
})
let multipartRequest : UploadAPIRequest<Foo,APIError> = tron.codable.uploadMultipart("foo", formData: { _ in })
multipartRequest.rxResult().subscribe(onNext: { result in
    print(result)
})

错误处理

TRON 包含用于错误的内置解析。 APIErrorErrorSerializable 协议的实现,其中包括几个有用的属性,可以从不成功的请求中获取

request.perform(withSuccess: { response in }, failure: { error in
    print(error.request) // Original URLRequest
    print(error.response) // HTTPURLResponse
    print(error.data) // Data of response
    print(error.fileURL) // Downloaded file url, if this was a download request
    print(error.error) // Error from Foundation Loading system
    print(error.serializedObject) // Object that was serialized from network response
  })

CRUD

struct Users
{
    static let tron = TRON(baseURL: "https://api.myapp.com")

    static func create() -> APIRequest<User,APIError> {
      tron.codable.request("users").post()
    }

    static func read(id: Int) -> APIRequest<User, APIError> {
        tron.codable.request("users/\(id)")
    }

    static func update(id: Int, parameters: [String:Any]) -> APIRequest<User, APIError> {
      tron.codable.request("users/\(id)").put().parameters(parameters)
    }

    static func delete(id: Int) -> APIRequest<User,APIError> {
      tron.codable.request("users/\(id)").delete()
    }
}

使用这些请求非常简单

Users.read(56).perform(withSuccess: { user in
  print("received user id 56 with name: \(user.name)")
})

为您的 API 引入命名空间也很好

enum API {}
extension API {
  enum Users {
    // ...
  }
}

这样您就可以像这样调用您的 API 方法

API.Users.delete(56).perform(withSuccess: { user in
  print("user \(user) deleted")
})

模拟

模拟功能内置于 APIRequest 本身。 您需要模拟成功的请求所需要做的就是设置 apiStub 属性并启用 stubbingEnabled

API.Users.get(56)
         .stub(with: APIStub(data: User.fixture().asData))
         .perform(withSuccess: { stubbedUser in
           print("received stubbed User model: \(stubbedUser)")
})

可以在 TRON 对象上全局启用模拟,也可以在单个 APIRequest 上本地启用模拟。 模拟不成功的请求也很容易

API.Users.get(56)
         .stub(with: APIStub(error: CustomError()))
         .perform(withSuccess: { _ in },
                  failure: { error in
  print("received stubbed api error")
})

您还可以选择延迟模拟时间

request.apiStub.stubDelay = 1.5

上传

let request = tron.codable.upload("photo", fromFileAt: fileUrl)
let request = tron.codable.upload("photo", data: data)
let request = tron.codable.upload("photo", fromStream: stream)
let request: UploadAPIRequest<EmptyResponse,MyAppError> = tron.codable.uploadMultipart("form") { formData in
    formData.append(data, withName: "cat", mimeType: "image/jpeg")
}
request.perform(withSuccess: { result in
    print("form sent successfully")
})

下载

let responseSerializer = TRONDownloadResponseSerializer { _,_, url,_ in url }
let request: DownloadAPIRequest<URL?, APIError> = tron.download("file",
                                                                to: destination,
                                                                responseSerializer: responseSerializer)

插件

TRON 包含插件系统,允许对大多数请求事件做出反应。

插件可以在 TRON 实例本身上全局使用,也可以在具体的 APIRequest 上本地使用。 请记住,添加到 TRON 实例的插件将为每个请求调用。 全局和本地插件有一些非常酷的用例。

默认情况下,不使用任何插件,但是作为 TRON 框架的一部分实现了两个插件。

NetworkActivityPlugin

NetworkActivityPlugin 用于监视请求并控制 iPhone 状态栏中的网络活动指示器。 此插件假定您的应用程序中只有一个 TRON 实例。

let tron = TRON(baseURL: "https://api.myapp.com", plugins: [NetworkActivityPlugin()])

NetworkLoggerPlugin

NetworkLoggerPlugin 用于以可读格式将响应记录到控制台。 默认情况下,它仅打印失败的请求,跳过成功的请求。

本地插件

本地插件有一些非常酷的概念,其中一些在专门的 PluginConcepts 页面中进行了描述。

替代方案

我们致力于构建尽可能最好的工具来与 RESTful Web 服务进行交互。 但是,我们理解每个工具都有其用途,因此,了解可以使用哪些其他工具来实现相同的目标始终是有用的。

TRON 受到 Moya framework 和 LevelUPSDK 的极大启发,后者已不再开源。

许可

TRON 在 MIT 许可证下发布。 有关详细信息,请参阅 LICENSE。

关于 MLSDev

MLSDev.com

TRONMLSDev, Inc. 维护。 我们专注于提供移动和 Web 开发中的一体化解决方案。 我们的团队遵循精益原则并根据敏捷方法工作,以交付最佳结果,从而减少开发预算及其时间表。

在此处 了解更多,请随时 与我们联系