首页 > 解决方案 > 将可编码结构编码为 Alamofire POST 参数 - Swift

问题描述

我正在尝试将我的前端 iOS 应用程序 (Swift) 连接到我的 Django 后端。

我尝试创建一个APIRouter处理任何请求的类。这个类需要符合URLRequestConvertible.

我有这个结构:

struct APIAccountID: Codable {
    
    let username: String
    let password: String
}

这是可编码的。

我正在尝试在 Alamofire POST 请求中传递此结构的数据。

所以我试图像这样对对象进行编码:

var parameters: [String: AnyObject]? {
    switch self {
    case .fetchAccessToken(let accountID):
        if let data = try? JSONEncoder().encode(accountID) {
            return data
        } else {
            print("Error: encoding post data")
            return nil
        }
    default: break
    }
}

使用AlamofireAny或. _AnyObjectURLEncodedFormParameterEncoderJSONParameterEncoder

如果我使用此参数开关:

var parameters: Data? {
        switch self {
        case .fetchAccessToken(let accountID):
            if let data = try? JSONEncoder().encode(accountID) {
                return data
            } else {
                print("[fetchAccessToken] Error: encoding post data")
            }
        default:
            break
        }
        return nil
    }

发送请求时出现此错误:

{
    "non_field_errors" =     (
        "Invalid data. Expected a dictionary, but got str."
    );
}

我的 URLRequestConvertible 函数:

// MARK: - URLRequestConvertible
extension APIRouter: URLRequestConvertible {
    
    func asURLRequest() throws -> URLRequest {
        let url = try baseURL.asURL().appendingPathComponent(path)
        var request = URLRequest(url: url)
        request.method = method
        if method == .get {
            request = try URLEncodedFormParameterEncoder()
                .encode(parameters, into: request)
        } else if method == .post {
            request = try JSONParameterEncoder().encode(parameters, into: request)
            request.setValue("application/json", forHTTPHeaderField: "Accept")
        }
        return request
    }
}

标签: iosswiftdjango

解决方案


AlamoFireURLFormEncodedParameterEncoderJSONParameterEncoder可以接受任何符合的Encodable,所以你不需要先使用JSONEncoder;他们会处理这些。

您的根本问题是,鉴于您将拥有不同的参数类型,具体类型应该是什么parameters,具体取决于 API 调用。

你不能说

var parameters: APIAccountID

因为这不适用于参数是 aupdatePortfolio或其他的情况。

你不能简单地说var parameters: Encodable?,因为你需要一个具体的类型。

您需要使用类型擦除。这是一种在 Swift 中使用的技术,允许将原本不相关的类型用作参数。有多种方法,但一种常见的方法是“装箱”,其中实际类型存储为属性,当您引用类型擦除类型时,您将该引用或函数调用重定向到装箱或包装类型。

因此,在这种情况下,您需要的是AnyEncodable-A 类型擦除的Encodable. 幸运的是,FlightSchool 存储库中提供了这样的东西 - https://github.com/Flight-School/AnyCodable

将该包添加到项目后,您可以修改parameters

var parameters: AnyEncodable? {
    switch self {
        case .fetchAccessToken(let accountID):
            return AnyEncodable(accountID)
        case .updatePortfolio(let portfolio):
            return AnyEncodable(portfolio)
    }
}

现在,我们的parametersvar 有一个固定的类型 - AnyEncodable?,它符合Encodable所以每个人都很高兴。该实现要求您AnyEncodable使用您的实际实例来实例化Encodable


推荐阅读