TRON 是一个基于 Alamofire 的轻量级网络抽象层。它可以用来极大地简化与 RESTful JSON 网络服务交互。
特性
- 基于协议的泛型实现
- 内置响应和错误解析
- 支持自定义映射器(默认使用 `SwiftyJSON` 实现)。默认使用 `
Codable
` 协议。 - 支持上传任务
- 支持下载任务和断点续传
- 优秀的插件系统
- 模拟网络请求
- 模块化架构
- 支持 iOS/Mac OS X/tvOS/watchOS/Linux
- 支持 CocoaPods/Swift Package Manager
- RxSwift / Combine 扩展
- 支持 `Swift Concurrency`
- 完整的文档
概览
我们设计 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)")
})
要求
- Xcode 13及以上
- Swift 5.3及以上
- iOS 11 / macOS 10.13 / tvOS 11.0 / watchOS 4.0
安装
Swift 包管理器
- 在项目设置中添加包 -> Swift 包
TRON 框架包含 Codable 实现是为了使用 SwiftyJSON,请导入 TRONSwiftyJSON
框架。使用 RxSwift 封装请使用 RxTRON
。
CocoaPods
pod 'TRON', '~> 5.3.0'
仅包含 Core 子规格,不携带 SwiftyJSON 依赖
pod 'TRON/Core', '~> 5.3.0'
TRON 的 RxSwift 扩展
pod 'TRON/RxSwift', '~> 5.3.0'
迁移指南
项目状态
TRON
在 MLSDev Inc. 的积极开发下。欢迎 Pull 请求!
请求构建
TRON
对象作为 APIRequest
的初始配置器,设置所有基本值,并配置使用 baseURL。
let tron = TRON(baseURL: "https://api.myapp.com/")
您需要保持对 TRON
对象的强引用,因为它持有了运行所有请求的 Alamofire.Manager。
URLBuildable
URLBuildable
协议用于将相对路径转换为 URL,该 URL 会被请求使用。
public protocol URLBuildable {
func url(forPath path: String) -> URL
}
默认情况下,TRON
使用 URLBuilder
类,该类简单地将相对路径附加到基本 URL 上,这对于大多数情况来说是足够的。您可以通过更改 TRON
上的 urlBuilder
属性或局部修改 APIRequest
上的 urlBuilder
属性来全局地自定义 URL 构建过程,以单个请求为范围。
发送请求
要发送 APIRequest
,请调用 APIRequest
上的 perform(withSuccess:failure:)
方法。
let alamofireRequest = request.perform(withSuccess: { result in }, failure: { error in})
或者,您可以使用包含 Alamofire.Response
的 performCollectingTimeline(withCompletion:)
方法。
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.ReadingOptions
,这些选项用于 SwiftyJSON.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
}
如果你更喜欢接受包含成功模型或 ErrorModel 的结果的响应,也可以这样做。
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
包含了内建的错误解析。 APIError
是 ErrorSerializable
协议的实现,它包含了一些有用的属性,可以从失败的请求中获取。
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
中。所有您需要做的就是要设置 API 的模拟响应属性并将模拟请求功能打开
API.Users.get(56)
.stub(with: APIStub(data: User.fixture().asData))
.perform(withSuccess: { stubbedUser in
print("received stubbed User model: \(stubbedUser)")
})
对于失败的请求的模拟也同样简单
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框架和LevelUPSDK的极大启发,后者已不再作为开源项目存在。
许可证
TRON
采用 MIT 许可协议发布。有关详细信息,请参阅 LICENSE 文件。
关于 MLSDev
TRON
由 MLSDev, Inc. 维护。我们专注于提供移动和网站开发的全套解决方案。我们的团队遵循精益原则,采用敏捷方法工作,以提供最佳结果,降低开发成本和项目时间表。