几天来我一直在努力通过此链接扩展 Matteo Manferdinis 网络架构https://matteomanferdini.com/swift-rest-api/#send-data-post-body
由于指南仅显示了如何实现 GET 方法,因此我还需要 POST 数据。所以我向 ApiResource 协议引入了可选的 body 属性:
protocol APIResource {
associatedtype ModelType: Codable
var baseUrl: String {get}
var apiVersion: String {get}
var path: String {get}
var method: HTTPMethod {get}
// var headers: [String: String]? {get}
// var parameters: [String: Any]? {get}
var filterStatus: String {get}
var body: ModelType? {get}
}
enum HTTPMethod: String {
case get = "GET"
case post = "POST"
}
extension APIResource {
var filterStatus: String {get {return ""}}
var apiVersion: String {get {return ""}}
var body: ModelType? {return nil}
var url: URL {
var components = URLComponents(string: baseUrl)!
components.path = "\(apiVersion)\(path)"
if filterStatus != "" {
components.queryItems = [URLQueryItem(name: "status", value: filterStatus)]
}
return components.url!
}
}
然后我构建一个适用于 APIResource 协议的 PostItemStruct 并初始化 body 属性。
struct PostItemResource: APIResource {
typealias ModelType = ItemType
var baseUrl = AWSAPI.baseUrl
var apiVersion = AWSAPI.version
var path = "/items/"
var method = HTTPMethod.post
var body : ItemType
init(item: ItemType) {
self.body = item
self.path = "/items/\(item.id)"
}
}
我的viewModel使用updateItem()函数来声明资源和请求并调用request.execute()函数。
func updateItem() async {
guard let item = item else { return }
let resource = PostItemResource(item: item)
let request = APIRequest(resource: resource)
errorMessage = nil
do {
try await request.execute()
} catch {
print((error as NSError).localizedFailureReason ?? "unknownError")
}
}
request.execute() 函数将检查它是否是 get / 或 post 方法,在这种情况下,它将调用 NetworkRequest 协议中的 upload(resource) 函数。
func execute() async throws -> (urlResponse: URLResponse, data: ModelType?) {
switch resource.method {
case .get:
try await load(resource.url)
case .post:
try await upload(resource)
}
}
这给我们带来了一个痛点,我无法再理解发生了什么:
func upload(_ postData: any APIResource) async throws -> (urlResponse: URLResponse, data: ModelType?) {
var request = URLRequest(url: postData.url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
print("\n\nFull postData:")
dump(postData)
print("\n\npostData.body:")
dump(postData.body)
guard let body = postData.body else {throw NetworkError.invalidInput}
request.httpBody = try encode(body as! Self.ModelType)
let (data, response) = try await URLSession.shared.data(for: request)
return try (response, decode(data))
}
}
在这里,我希望 postData.body 包含我要上传的数据。但是转储(postData.body)给了我一个零,并且警卫将我从函数中抛出。 但是转储(postData)给了我一个输出,表明主体不为零。
下面您可以看到控制台,其中包含我的上传功能的两个转储。谁能解释一下为什么 postData.body 在直接寻址时会转换为 nil ?
Full postData:
▿ App.PostItemResource
- baseUrl: "xxxx"
- apiVersion: "/beta"
- path: "/item/498AEF35-844E-467E-A782-E1FFF11D4CCA"
- method: App.HTTPMethod.post
▿ body: App.ItemType
- id: "498AEF35-844E-467E-A782-E1FFF11D4CCA"
▿ version: Optional(0)
- some: 0
- type: App.MyType.einTyp
- status: App.MyStatus.stock
postData.body:
- nil
在架构的多个级别上尝试转储数据,并且在 URLSession 之前它在所有地方都工作正常。这里 postData.body 因不明原因变为 nil。
APIResource
需要这个:
var body: ModelType? { get }
在
PostItemResource
中,ModelType
是ItemType
,所以应该有一个
var body: ItemType? { get }
但是你写道:
var body: ItemType
不满足协议要求。相反,您在
extension
中声明的默认实现可以满足协议要求,它只返回 nil
。
所以只需将
body
声明为可选,一切都应该没问题。
var body: ItemType?
如果不想让人误将
body
设置为nil
,可以改为let
,或者有专门的设置方法:
private(set) var body: ItemType?
func setBody(_ item: ItemType) {
body = item
}