NerdzNetworking
是对 URLSession
和 URLRequest
的封装,旨在简化使用 Swift
语言创建和管理网络请求的过程。
您需要定义请求。
class LoginWithFacebookRequest: Request {
typealias ResponseObjectType = User
typealias ErrorType = AuthError
let path = "login/facebook"
let methong = .post
let body: RequestBody?
init(token: String) {
body = .params(
[
"token": token
]
)
}
}
然后直接使用即可。(每个调用都是可选的,可以根据需要跳过)
LoginWithFacebookRequest(token: fbToken)
.execute()
.onSuccess { user in
...
}
.onFail { error in
...
}
.onStart { operation in
...
}
.onProgress { progress in
...
}
.onDebug { info in
...
}
}
创建 NerdzNetworking
库的主要理念是将网络请求最大程度地拆分成小块。理想情况是每个请求都有一个单独的类/结构体。 这应该有助于在文件结构中轻松导航和搜索特定请求的信息。
该库试图遵循的另一个流程是预定义的选项和使用的简便性。 我们尝试在协议之上使用泛型类型,以及使用枚举而不是原始值。 例如 - 预定义的标头,如 Content-Type
或 Accept
。 我们没有提供任何值的可能性,而是定义了枚举,将可能的输入限制为仅预定义的场景,例如 .application(.json)
。 为了简单起见,前面提到的标头已经为您预先选择了 .application(.json)
值,因此如果您使用的是标准 REST API,则一切都已准备就绪。
首先,您需要设置将在以后用于执行请求的端点。 为此,您应该使用 Endpoint
类。 Endpoint
类将收集执行请求的所有常规设置。 您可以随时更改任何参数。
let endpoint = Endpoint(baseUrl: myBaseUrl)
endpoint.headers = defaultHeaders // Specifying some default headers like OS, device language, device model, etc.
endpoint.headers.authToken = .bearer(tokenString) // Specifying user token
创建端点后,您可以将其标记为 default
,以便每个请求都会自动拾取它。
Endpoint.default = endpoint
您可以根据需要更改 default
端点配置或环境。
要创建请求,您应该实现 Request
协议。 您可以为每个请求使用单独的类或 enum
。
class MyRequest: Request {
typealias ResponseObjectType = MyExpectedResponse
typealias ErrorType = MyUnexpectedError
let path = "my/path/to/backend" // Required
let methong = .get // Optional
let queryParams = [("key", "value")] // Optional
let body = .params(["key", "value"]) // Optional
let headers = [RequestHeaderKey("key"): "value", .contentType: "application/json"] // Optional
let timeout = 60 // Optional, by defauld will be picked from Endpoint
let endpoint = myEndpoint // Optional, by default will be a Endpoint.default
}
这只是一个示例,可能您不会拥有所有必需和静态的参数。 要动态创建请求 - 您可以使用初始化器,这些初始化器将采用请求所需的动态参数。 例如 - 一些动态 bodyParams 或动态路径。
您可以使用内置类 DefaultRequest
执行请求,而无需创建单独的类。
let myRequest = DefaultRequest<MyExpectedResponse, MyUnexpectedError>(
path: "my/path/to/backend",
method: .get,
queryParams: [("key", "value")],
body: .params(["key", "value"]),
headers: [RequestHeaderKey("key"): "value", .contentType: "application/json"],
timeout: 60,
responseConverter: myResponseConverter,
errorConverter: myErrorConverter,
endpoint: myEndpoint)
NerdzNetworking
库还提供了一种简单的方法来创建和执行 multipart form-data 请求。 您只需要实现 MultipartFormDataRequest
而不是 Request
协议,或者使用 DefaultMultipartFormDataRequest
类。
除了 Request
字段之外,您还需要提供 files
字段,该字段是 MultipartFile
协议实例。 您可以实现此协议或使用 DefaultMultipartFile
类。
class MyMultipartRequest: MultipartFormDataRequest {
// Same fields as in Request example
let files: [MultipartFile] = [
DefaultMultipartFile(resource: fileData, mime: .image(.png), fileName: "avatar1"),
DefaultMultipartFile(resource: fileUrl, mime: .audio(.mp4), fileName: "song"),
DefaultMultipartFile(resource: filePath, mime: .image(.jpeg), fileName: "avatar2")
]
}
要执行请求,您可以使用以下构造
myRequest.execute()
:将在 Endpoint.default
上执行 myRequest
myRequest.execute(on: myEndpoint)
:将在 myEndpoint
上执行 myRequest
myEndpoint.execute(myRequest)
:将在 myEndpoint
上执行 myRequest
要处理执行过程,您可以在调用 execute
方法后使用 future-style 方法。(每个方法都是可选的,因此只使用您真正需要的那些)
myRequest
.execute()
.responseOn(.main) // Response will be returned in `.main` queue
.retryOnFail(false) // If this request will fail - system will not try to rerun it again
.onStart { requestOperation in
// Will be called when request will start and return request operation that allow to control request during the execution
}
.onProgress { progress in
// Will provide a progress of request execution. Useful for multipart uploading requests
}
.onSuccess { response in
// Will return a response object specified in request under `ResponseType`
}
.onFail { error in
// Will return `ErrorResponse` that might contain `ErrorType` specified in request
}
.onDebug { info in
// Will return `DebugInfo` that contain a list of useful information for debugging request failure
}
目前,NerdzNetworking
库仅支持原生 Codable
映射。 教程。
NerdzNetworking
支持响应转换器,可以在映射过程之前转换响应。 如果您需要将响应数据调整为内部模型,或者绕过父对象仅映射子对象,这可能很有用。
您还可以提供响应转换器,以便在映射到预期响应开始之前转换一些未正确返回的响应。 此处的负责协议是 ResponseJsonConverter
。
响应转换器应在 Request
类中的 responseConverter
(成功)或/和 errorConverter
(失败)字段下指定。
您可以拥有实现 ResponseJsonConverter
协议的自己的转换器,或者使用内置实现:KeyPathResponseConverter
、ClosureResponseConverter
。
KeyPathResponseConverter
允许您通过到节点的特定 path
从 JSON
子节点中提取数据。
class MyRequest: Request {
var responseConverter: ResponseJsonConverter {
KeyPathResponseConverter(path: "path/to/node")
}
var errorConverter: ResponseJsonConverter {
KeyPathResponseConverter(path: "path/to/error")
}
}
ClosureResponseConverter
允许您通过闭包提供自定义转换。 您将需要提供一个 closure
,该闭包接受 Any
并在转换后返回 Any
。
class MyRequest: Request {
var responseConverter: ResponseJsonConverter {
ClosureResponseConverter { response in
// Return converted response for success response
}
}
var errorConverter: ResponseJsonConverter {
ClosureResponseConverter { response in
// Return converted response for error response
}
}
}
您可以通过实现 ResponseJsonConverter
协议来实现您自己的转换器。
class MyResponseConverter: ResponseJsonConverter {
func convertedJson(from json: Any) throws -> Any {
// Provide convertation and return converted code
}
}
您可以使用 CocoaPods 依赖管理器来安装 NerdzNetworking
。 在您的 Podfile
中指定
pod 'NerdzNetworking', '~> 1.0.1'
要将 NerdzNetworking 添加到基于 Swift Package Manager 的项目中,请添加
.package(url: "https://github.com/nerdzlab/NerdzNetworking")
表示端点的类,其中包含执行请求的所有设置。 您需要创建至少一个实例才能执行请求。
名称 | 类型 | 可访问性 | 描述 |
---|---|---|---|
Endpoint.default |
Endpoint |
static read-write |
将用于执行请求的端点实例 |
baseUrl |
URL |
readonly |
端点基本 URL |
decoder |
JSONDecoder? |
如果提供,则将默认用于解码 json 响应的解码器 | |
responseQueue |
DispatchQueue? |
将默认用于调度请求完成的队列 | |
retryingCount |
Int |
在中止请求执行错误之前应发生的重试次数 | |
observation |
ObservationManager |
一个管理器,您应该在其中注册观察者,以观察不同类型的请求/响应 | |
requestRetrying |
RequestRetryingManager |
一个管理器,您应该在其中注册重试器 | |
sessionConfiguration |
URLSessionConfiguration |
readonly |
用于内部 URLSession 的配置 |
headers |
[RequestHeaderKey: String] |
read-write |
将与每个请求一起使用的标头 |
init(
baseUrl : URL,
decoder : JSONDecoder? = nil,
responseQueue : DispatchQueue? = nil,
sessionConfiguration: URLSessionConfiguration = .default,
retryingCount : Int = 1,
headers : [RequestHeaderKey: String] = [:]
)
使用所有参数进行初始化
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
baseUrl |
URL |
端点基本 URL | |
decoder |
JSONDecoder? |
nil |
JSON 响应的默认解码器 |
responseQueue |
DispatchQueue |
nil |
完成的默认响应队列 |
sessionConfiguration |
URLSessionConfiguration |
.default |
将用于内部 URLSession 的配置 |
retryingCount |
Int |
1 |
请求失败的重试次数 |
headers |
[RequestHeaderKey: String] |
[:] |
将与每个请求一起使用的标头 |
func execute<T: Request>(_ request: T) -> ResponseInfoBuilder<T>
在当前端点上执行请求
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
request |
Request |
要执行的请求 |
func cURL<T: Request>(for request: T) throws -> String
返回所提供请求的 cURL 字符串表示形式
对于比较来自 Postman 或 Proxyman 等应用程序和代码的请求,或从终端快速执行请求非常有用。
func useAsDefault()
将当前实例设置为未来执行的 default
泛型:T: Request
一个表示请求操作执行参数(如 queue
)和完成(如 onSuccess
)的类。
名称 | 类型 | 可访问性 | 描述 |
---|---|---|---|
request |
T |
readonly |
在操作中使用的请求 |
func response(on queue: DispatchQueue) -> Self
设置将调用所有完成的队列。 默认情况下将使用 main
队列
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
queue |
DispatchQueue |
将调用完成的队列 |
func decode(with decoder: JSONDecoder) -> Self
设置将用于解码 JSON 响应或错误的解码器
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
decoder |
JSONDecoder |
用于响应 JSON 解码的解码器 |
func retryOnFail(_ retryingCount: Int) -> Self
为请求失败设置重试计数。 默认情况下等于 1
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
retryingCount |
Int |
重试计数数字 |
func onSuccess(_ closure: @escaping ResponseSuccessCallback) -> Self
ResponseSuccessCallback = (T.ResponseObjectType) -> Void
注册如果请求成功将调用的闭包
您可以多次调用此方法,并且将触发每个注册的闭包
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
closure |
(T.ResponseObjectType) -> Void |
触发的闭包 |
func onFail(_ closure: @escaping FailCallback) -> Self
FailCallback = (ErrorResponse<T.ErrorType>) -> Void
注册如果请求失败将调用的闭包
您可以多次调用此方法,并且将触发每个注册的闭包
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
closure |
(ErrorResponse<T.ErrorType>) -> Void |
触发的闭包 |
func onProgress(_ closure: @escaping ProgressCallback) -> Self
ProgressCallback = (Double) -> Void
注册将返回请求处理进度的闭包
您可以多次调用此方法,并且将触发每个注册的闭包
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
closure |
(Double) -> Void |
触发的闭包 |
func onDebug(_ closure: @escaping DebugCallback) -> Self
DebugCallback = (DebugInfo) -> Void
注册将返回请求和响应信息以进行调试的闭包
您可以多次调用此方法,并且将触发每个注册的闭包
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
closure |
(DebugInfo) -> Void |
触发的闭包 |
func onStart(_ closure: @escaping StartCallback) -> Self
StartCallback = () -> Void
注册将在请求开始处理时触发的闭包
您可以多次调用此方法,并且将触发每个注册的闭包
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
closure |
() -> Void |
触发的闭包 |
func calncel()
取消请求处理
类型:protocol
表示单个请求的协议。 您可以实现此协议,然后执行它。 您可以使用已实现此协议的 DefaultRequest
结构来执行请求。
名称 | 类型 | 可访问性 | 描述 |
---|---|---|---|
ResponseObjectType |
ResponseObject |
来自服务器的预期响应的类型。 它应该实现 ResponseObject 协议 |
|
ErrorType |
ServerError |
来自服务器的预期错误的类型。 它应该实现 ServerError 协议 |
名称 | 类型 | 可访问性 | 描述 |
---|---|---|---|
path |
String |
get required |
请求路径 |
method |
HTTPMethod |
get required |
请求方法 |
queryParams |
[(String, String)] |
get optional |
请求查询参数,表示为元组数组以保存顺序 |
body |
RequestBody? |
get optional |
请求正文 |
headers |
[RequestHeaderKey: String] |
get optional |
特定于请求的标头。 将添加到来自 Endpoint 的标头中 |
timeout |
TimeInterval |
get optional |
请求超时。 如果未指定,将使用来自 Endpoint 的默认值 |
responseConverter |
ResponseJsonConverter? |
get optional |
成功的响应转换器。 将在映射到 ResponseObjectType 之前进行转换 |
errorConverter |
ResponseJsonConverter? |
get optional |
错误响应转换器。 将在映射到 ErrorType 之前进行转换 |
endpoint |
Endpoint? |
get optional |
将用于执行的端点 |
decoder |
JSONDecoder? |
get optional |
将用于解码响应的 JSON 响应解码器。 如果未提供,将使用来自 Endpoint 的解码器 |
func execute(on endpoint: Endpoint) -> ResponseInfoBuilder<Self>
在提供的端点上执行当前请求
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
endpoint |
Endpoint |
当前请求将在其上执行的端点。 |
func execute() -> ResponseInfoBuilder<Self>
在 Endpoint.default
实例上执行当前请求
类型:struct
实现:Request
Request
协议的默认实现,可用于执行请求而无需创建额外的类
名称 | 类型 | 描述 |
---|---|---|
响应 |
ResponseObject |
成功响应对象的类型 |
错误 |
ServerError |
错误响应对象的类型 |
名称 | 类型 | 可访问性 | 描述 |
---|---|---|---|
path |
String |
read-write |
请求路径 |
method |
HTTPMethod |
read-write |
请求方法 |
queryParams |
[(String, String)] |
read-write |
请求查询参数,表示为元组数组以保存顺序 |
body |
RequestBody? |
read-write |
请求正文 |
headers |
[RequestHeaderKey: String] |
read-write |
特定于请求的标头。 将添加到来自 Endpoint 的标头中 |
timeout |
TimeInterval |
read-write |
请求超时。 如果未指定,将使用来自 Endpoint 的默认值 |
responseConverter |
ResponseJsonConverter? |
read-write |
成功的响应转换器。 将在映射到 ResponseObjectType 之前进行转换 |
errorConverter |
ResponseJsonConverter? |
read-write |
错误响应转换器。 将在映射到 ErrorType 之前进行转换 |
endpoint |
Endpoint? |
read-write |
将用于执行的端点 |
decoder |
JSONDecoder? |
read-write |
将用于解码响应的 JSON 响应解码器。 如果未提供,将使用来自 Endpoint 的解码器 |
init(
path : String,
method : HTTPMethod,
queryParams : [(String, String)] = [],
body : RequestBody? = nil,
headers : [RequestHeaderKey: String] = [:],
timeout : TimeInterval? = nil,
responseConverter : ResponseJsonConverter? = nil,
errorConverter : ResponseJsonConverter? = nil,
endpoint : Endpoint? = nil,
decoder : JSONDecoder? = nil
)
使用所有可能的参数初始化 DefaultRequest
对象
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
path |
String |
- | 请求路径 |
method |
HTTPMethod |
- | 请求方法 |
queryParams |
[(String, String)] |
[] |
请求查询参数,表示为元组数组以保存顺序 |
bodyParams |
[String: Any] |
[:] |
请求体参数 |
headers |
[RequestHeaderKey: String] |
[:] |
特定于请求的标头。 将添加到来自 Endpoint 的标头中 |
timeout |
TimeInterval |
nil |
请求超时。 如果未指定,将使用来自 Endpoint 的默认值 |
responseConverter |
ResponseJsonConverter? |
nil |
成功的响应转换器。 将在映射到 ResponseObjectType 之前进行转换 |
errorConverter |
ResponseJsonConverter? |
nil |
错误响应转换器。 将在映射到 ErrorType 之前进行转换 |
endpoint |
Endpoint? |
nil |
将用于执行的端点 |
decoder |
JSONDecoder? |
nil |
将用于解码响应的 JSON 响应解码器。 如果未提供,将使用来自 Endpoint 的解码器 |
类型:protocol
继承自: Request
协议
表示 multipart form-data 请求的协议。该协议继承了 Request
协议,并在其基础上添加了 files 属性。因此,它与 Request
协议基本相同。您可以使用 DefaultMultipartFormDataRequest
结构体,该结构体已经实现了此协议,来执行 multipart 请求。
名称 | 类型 | 可访问性 | 描述 |
---|---|---|---|
ResponseObjectType |
ResponseObject |
来自服务器的预期响应的类型。 它应该实现 ResponseObject 协议 |
|
ErrorType |
ServerError |
来自服务器的预期错误的类型。 它应该实现 ServerError 协议 |
名称 | 类型 | 可访问性 | 描述 |
---|---|---|---|
path |
String |
get required |
请求路径 |
method |
HTTPMethod |
get required |
请求方法 |
queryParams |
[(String, String)] |
get optional |
请求查询参数,表示为元组数组以保存顺序 |
body |
RequestBody? |
get optional |
请求正文 |
headers |
[RequestHeaderKey: String] |
get optional |
特定于请求的标头。 将添加到来自 Endpoint 的标头中 |
timeout |
TimeInterval |
get optional |
请求超时。 如果未指定,将使用来自 Endpoint 的默认值 |
responseConverter |
ResponseJsonConverter? |
get optional |
成功的响应转换器。 将在映射到 ResponseObjectType 之前进行转换 |
errorConverter |
ResponseJsonConverter? |
get optional |
错误响应转换器。 将在映射到 ErrorType 之前进行转换 |
endpoint |
Endpoint? |
get optional |
将用于执行的端点 |
decoder |
JSONDecoder? |
get optional |
将用于解码响应的 JSON 响应解码器。 如果未提供,将使用来自 Endpoint 的解码器 |
files |
[MultipartFile] |
get required |
需要随请求一起处理的文件列表 |
func execute(on endpoint: Endpoint) -> ResponseInfoBuilder<Self>
在提供的端点上执行当前请求
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
endpoint |
Endpoint |
当前请求将在其上执行的端点。 |
func execute() -> ResponseInfoBuilder<Self>
在 Endpoint.default
实例上执行当前请求
类型:struct
实现: MultipartFormDataRequest
MultipartFormDataRequest
协议的默认实现,可用于执行 multipart 请求而无需创建额外的类
名称 | 类型 | 描述 |
---|---|---|
响应 |
ResponseObject |
成功响应对象的类型 |
错误 |
ServerError |
错误响应对象的类型 |
名称 | 类型 | 可访问性 | 描述 |
---|---|---|---|
path |
String |
read-write |
请求路径 |
method |
HTTPMethod |
read-write |
请求方法 |
queryParams |
[(String, String)] |
read-write |
请求查询参数,表示为元组数组以保存顺序 |
body |
RequestBody? |
read-write |
请求正文 |
headers |
[RequestHeaderKey: String] |
read-write |
特定于请求的标头。 将添加到来自 Endpoint 的标头中 |
timeout |
TimeInterval |
read-write |
请求超时。 如果未指定,将使用来自 Endpoint 的默认值 |
responseConverter |
ResponseJsonConverter? |
read-write |
成功的响应转换器。 将在映射到 ResponseObjectType 之前进行转换 |
errorConverter |
ResponseJsonConverter? |
read-write |
错误响应转换器。 将在映射到 ErrorType 之前进行转换 |
endpoint |
Endpoint? |
read-write |
将用于执行的端点 |
decoder |
JSONDecoder? |
read-write |
将用于解码响应的 JSON 响应解码器。 如果未提供,将使用来自 Endpoint 的解码器 |
files |
[MultipartFile] |
read-write |
需要在请求中处理的文件列表 |
init(
path : String,
method : HTTPMethod,
queryParams : [(String, String)] = [],
body : RequestBody? = nil,
headers : [RequestHeaderKey: String] = [:],
timeout : TimeInterval? = nil,
responseConverter : ResponseJsonConverter? = nil,
errorConverter : ResponseJsonConverter? = nil,
endpoint : Endpoint? = nil,
decoder : JSONDecoder? = nil,
files : [MultipartFile] = []
)
使用所有可能的参数初始化 DefaultMultipartFormDataRequest
对象
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
path |
String |
- | 请求路径 |
method |
HTTPMethod |
- | 请求方法 |
queryParams |
[(String, String)] |
[] |
请求查询参数,表示为元组数组以保存顺序 |
bodyParams |
[String: Any] |
[:] |
请求体参数 |
headers |
[RequestHeaderKey: String] |
[:] |
特定于请求的标头。 将添加到来自 Endpoint 的标头中 |
timeout |
TimeInterval |
nil |
请求超时。 如果未指定,将使用来自 Endpoint 的默认值 |
responseConverter |
ResponseJsonConverter? |
nil |
成功的响应转换器。 将在映射到 ResponseObjectType 之前进行转换 |
errorConverter |
ResponseJsonConverter? |
nil |
错误响应转换器。 将在映射到 ErrorType 之前进行转换 |
endpoint |
Endpoint? |
nil |
将用于执行的端点 |
decoder |
JSONDecoder? |
nil |
将用于解码响应的 JSON 响应解码器。 如果未提供,将使用来自 Endpoint 的解码器 |
files |
[MultipartFile] |
[] |
需要在请求中处理的文件列表 |
MultipartFile
协议的默认实现,如果您不想为发送 multipart 请求创建额外的类,可以使用它
名称 | 类型 | 可访问性 | 描述 |
---|---|---|---|
fileName |
String |
read-write |
文件名 |
mime |
MimeType |
read-write |
文件 mime 类型 |
resource |
MultipartResourceConvertable |
read-write |
文件数据的表示形式。可能是 String 、Data 、URL 、InputStream |
init(
resource: MultipartResourceConvertable,
mime : MimeType,
fileName: String
)
使用所有可能的参数初始化 DefaultMultipartFile
对象
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
resource |
MultipartResourceConvertable |
文件名 | |
mime |
MimeType |
文件 mime 类型 | |
fileName |
String |
文件数据的表示形式。可能是 String 、Data 、URL 、InputStream |
表示从服务器返回的错误的协议
名称 | 类型 | 可访问性 | 描述 |
---|---|---|---|
message |
String |
get required |
错误消息 |
String
Optional (可选)
类型: enum
(枚举)
继承自: String
表示请求 http 方法的枚举。
名称 | 描述 |
---|---|
.get |
GET http 方法 |
.post |
POST http 方法 |
.put |
PUT http 方法 |
.delete |
DELETE http 方法 |
.path |
PATH http 方法 |
类型: enum
(枚举)
表示不同类型的请求体的枚举
名称 | 参数 | 描述 |
---|---|---|
.raw |
value: Data |
原始数据 |
.string |
value: String |
字符串体 |
.params |
value: [String: Any] |
使用参数形成的正文 |
类型:struct
表示用于调试网络请求的信息。
可以从
ExecutionOperation
类接收。
名称 | 类型 | 可访问性 | 描述 |
---|---|---|---|
sessionConfiguration |
URLSessionConfiguration |
readonly |
当前请求的 URLSession 配置 |
request |
URLRequest |
readonly |
为执行而构建的 URLRequest |
dataResponse |
Data? |
readonly |
Data 格式的响应 |
urlResponse |
HTTPURLResponse |
readonly |
系统返回的响应 |
errorResponse |
Error? |
readonly |
系统返回的响应错误 |
requestDuration |
TimeInterval |
readonly |
请求执行持续时间 |
cURL |
String? |
readonly |
cURL 格式的请求表示 |
stringResponse |
String? |
readonly |
String 格式的响应 |
jsonResponse |
Any? |
readonly |
JSON 格式的响应 |
Combine
支持此代码在 MIT 许可证下分发。 有关更多信息,请参见 LICENSE
文件。