Request

swift 5.1 SwiftUI iOS macOS tvOS Build codecov

安装 - 开始使用 - 构建请求 - Codable - Combine - 工作原理 - 请求组 - 请求链 - Json - 贡献 - 许可证

与 SwiftUI 一起使用

安装

可以通过 Swift Package Manager 安装 swift-request

在 Xcode 11 中,转到 File > Swift Packages > Add Package Dependency...,然后粘贴 https://github.com/carson-katri/swift-request

现在只需 import Request,就可以开始使用

开始使用

旧方法

var request = URLRequest(url: URL(string: "https://jsonplaceholder.typicode.com/todos")!)
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: url!) { (data, res, err) in
    if let data = data {
        ...
    } else if let error = error {
        ...
    }
}
task.resume()

声明式 方法

Request {
    Url("https://jsonplaceholder.typicode.com/todo")
    Header.Accept(.json)
}
.onData { data in
    ...
}
.onError { error in
    ...
}
.call()

当您的数据变得更复杂时,声明请求的好处就会变得非常明显

Request {
    Url("https://jsonplaceholder.typicode.com/posts")
    Method(.post)
    Header.ContentType(.json)
    Body(Json([
        "title": "foo",
        "body": "bar",
        "usedId": 1
    ]).stringified)
}

构建好 Request 后,您可以指定要使用的响应处理程序。可以使用 .onData.onString.onJson.onError。您可以将它们链接在一起以处理多种响应类型,因为它们会返回修改后的 Request 版本。

要执行 Request,只需使用 .call()。这将运行 Request,并在完成后为您提供响应。

Request 也符合 Publisher,因此您可以像任何其他 Combine 发布者一样操作它(阅读更多

let cancellable = Request {
    Url("https://jsonplaceholder.typicode.com/todo")
    Header.Accept(.json)
}
.sink(receiveCompletion: { ... }, receiveValue: { ... })

构建请求

有许多不同的工具可用于构建 Request

每个 Request 中必须恰好存在一个

Url("https://example.com")
Url(protocol: .secure, url: "example.com")

设置 RequestMethodType(默认为 .get

Method(.get) // Available: .get, .head, .post, .put, .delete, .connect, .options, .trace, and .patch 

设置 HTTP 标头字段

Header.Any(key: "Custom-Header", value: "value123")
Header.Accept(.json)
Header.Authorization(.basic(username: "carsonkatri", password: "password123"))
Header.CacheControl(.noCache)
Header.ContentLength(16)
Header.ContentType(.xml)
Header.Host("en.example.com", port: "8000")
Header.Origin("www.example.com")
Header.Referer("redirectfrom.example.com")
Header.UserAgent(.firefoxMac)

创建查询字符串

Query(["key": "value"]) // ?key=value

设置请求正文

Body(["key": "value"])
Body("myBodyContent")
Body(myJson)

设置请求或资源的超时

Timeout(60)
Timeout(60, for: .request)
Timeout(30, for: .resource)

直接添加参数

重要提示: 您必须创建逻辑来处理自定义 RequestParam。您也可以考虑向 RequestParamType 添加一个 case。如果您认为您的自定义参数可能对其他人有用,请参阅贡献

Codable

让我们看一个例子。这里我们定义我们的数据

struct Todo: Codable {
    let title: String
    let completed: Bool
    let id: Int
    let userId: Int
}

现在我们可以使用 AnyRequest 从服务器提取 Todo 数组

AnyRequest<[Todo]> {
    Url("https://jsonplaceholder.typicode.com/todos")
}
.onObject { todos in ... }

在这种情况下,onObject 在响应中给我们 [Todo]?。获取数据并解码它就是这么简单。

Request 基于 AnyRequest 构建,因此它们支持所有相同的参数。

如果在标准 Request 上使用 onObject,您将收到 Data 作为响应。

Combine

RequestRequestGroup 都符合 Publisher

Request {
    Url("https://jsonplaceholder.typicode.com/todos")
}
.sink(receiveCompletion: { ... }, receiveValue: { ... })

RequestGroup {
    Request {
        Url("https://jsonplaceholder.typicode.com/todos")
    }
    Request {
        Url("https://jsonplaceholder.typicode.com/posts")
    }
    Request {
        Url("https://jsonplaceholder.typicode.com/todos/1")
    }
}
.sink(receiveCompletion: { ... }, receiveValue: { ... })

Request 使用 URLSession.DataTaskPublisher 发布结果。 RequestGroup 收集其正文中每个 Request 的结果,并发布结果数组。

您可以对 Request 使用所有您期望的 Combine 运算符

Request {
    Url("https://jsonplaceholder.typicode.com/todos")
}
.map(\.data)
.decode([Todo].self, decoder: JSONDecoder())
.sink(receiveCompletion: { ... }, receiveValue: { ... })

但是,Request 还附带了几个方便的 Publishers 来简化解码过程

  1. objectPublisher - 使用 JSONDecoder 解码 AnyRequest 的数据
  2. stringPublisher - 将数据解码为 String
  3. jsonPublisher - 将结果转换为 Json 对象

这是一个使用 objectPublisher 的例子

AnyRequest<[Todo]> {
    Url("https://jsonplaceholder.typicode.com/todos")
}
.objectPublisher
.sink(receiveCompletion: { ... }, receiveValue: { ... })

这消除了不断使用 .map.decode 来提取所需的 Codable 结果的需要。

要处理错误,您可以在 sink 中使用 receiveCompletion 处理程序

Request {
    Url("https://jsonplaceholder.typicode.com/todos")
}
.sink(receiveCompletion: { res in
    switch res {
    case let .failure(err):
        // Handle `err`
    case .finished: break
    }
}, receiveValue: { ... })

工作原理

Request 的主体是使用 RequestBuilder @resultBuilder 构建的。

它将正文中的每个 RequestParam 合并到一个 CombinedParam 对象中。 这包含所有其他参数作为子参数。

运行 .call() 时,会过滤子项以查找 Url,以及可能包含的任何其他可选参数。

有关更多信息,请参阅 RequestBuilder.swiftRequest.swift

请求组

RequestGroup 可用于同时运行多个 Request。 当每个 Request 完成(或失败)时,您都会收到响应

RequestGroup {
    Request {
        Url("https://jsonplaceholder.typicode.com/todos")
    }
    Request {
        Url("https://jsonplaceholder.typicode.com/posts")
    }
    Request {
        Url("https://jsonplaceholder.typicode.com/todos/1")
    }
}
.onData { (index, data) in
    ...
}
.call()

请求链

RequestChain 用于一次一个运行多个 Request。 当一个完成后,它将其数据传递给下一个 Request,因此您可以使用它来构建 Request

RequestChain.call 可以选择接受一个回调,该回调为您提供每个 Request 完成后的所有数据。

注意: 您必须使用 Request.chained 来构建您的 Request。 这使您可以访问先前 Request 的数据和错误。

RequestChain {
    Request.chained { (data, errors) in
        Url("https://jsonplaceholder.typicode.com/todos")
    }
    Request.chained { (data, errors) in
        let json = Json(data[0]!)
        return Url("https://jsonplaceholder.typicode.com/todos/\(json?[0]["id"].int ?? 0)")
    }
}
.call { (data, errors) in
    ...
}

重复调用

.update 用于在初始调用之后运行其他调用。 您可以传递一个数字或一个自定义 Publisher。 您还可以将多个 .update 链接在一起。 以下示例中的两个 .update 是等效的,因此最终结果是 Request 将立即调用一次,然后每 10 秒调用两次。

Request {
    Url("https://jsonplaceholder.typicode.com/todo")
}
.update(every: 10)
.update(publisher: Timer.publish(every: 10, on: .main, in: .common).autoconnect())
.call()

如果要使用 Request 作为 Publisher,请使用 updatePublisher

Request {
    Url("https://jsonplaceholder.typicode.com/todo")
}
.updatePublisher(every: 10)
.updatePublisher(publisher: ...)
.sink(receiveCompletion: { ... }, receiveValue: { ... })

update 不同,updatePublisher 不会立即发送值,而是会等待来自 Publisher 的第一个值。

Json

swift-request 包含对 Json 的支持。 Json 用作 Request 对象上的 onJson 回调中的响应类型。

您可以通过解析 StringData 来创建 Json

Json("{\"firstName\":\"Carson\"}")
Json("{\"firstName\":\"Carson\"}".data(using: .utf8))

您可以像预期的那样下标 Json

myJson["firstName"].string // "Carson"
myComplexJson[0]["nestedJson"]["id"].int

它还支持 dynamicMemberLookup,因此您可以像这样下标它

myJson.firstName.string // "Carson"
myComplexJson[0].nestedJson.id.int

您可以使用 .string.int.double.bool.array 以所需的类型检索值。

注意: 这些返回非可选值。 如果要检查 nil,可以使用 .stringOptional.intOptional 等。

贡献

请参阅 CONTRIBUTING

许可证

请参阅 LICENSE