PAYATHON 2026

Swift URLSession 如何统一处理带 token 和不带 token 的请求?

数据阿明

可以共用一个请求方法。底层请求方法可以将 token 设为可选参数:有非空值时添加 Authorization 请求头,否则省略。是否必须登录,由接口的认证要求决定。

原代码需要调整返回结果和错误处理:用 Result<Data, Error> 替代 (Data?, Any?),检查 HTTP 状态码,并确保无效 URL 等失败路径也会调用 completion handler。

原代码有哪些问题?

方法名与实际请求不一致

newPostRequest 设置的是:

request.httpMethod = "GET"

如果目标是读取数据,可以将方法改名为 get。如果接口要求 POST,则需要按照接口约定修改 HTTP 方法和请求体。由于当前问题没有提供 POST 请求体,下面的示例保留 GET。

没有 token 不一定是错误

当前代码在 token 为空时打印 invalid Token,随后仍然发送请求。公开接口不传 token 是正常情况,不需要打印错误。

可以将参数改成:

token: String? = nil

这样调用公开接口时就可以省略 token。本地检查只能判断 token 是否存在,不能判断它是否有效、过期或具备权限;认证结果仍由服务端决定。

completion handler 存在含义不清的状态

原来的类型是:

typealias completion = ((Data?, Any?) -> ())

Any? 无法明确表达错误类型,completionHandler(nil, nil) 也让调用方无法判断失败原因。此外,URL 无效时直接 return,调用方收不到完成通知,加载状态可能一直无法结束。

改成 Result<Data, Error> 后,结果分为成功和失败两种情况。

不能只检查 error

URLSession 回调中的 error 主要用于报告传输等错误。收到 HTTP 401、404 或 500 时,error 仍可能为 nil,而 data 中可能是服务端返回的错误内容。

因此,需要读取 HTTPURLResponse.statusCode,按照接口约定判断请求是否成功。下面使用常见的 200..<300 范围。

一个可复用的 GET 请求实现

这个版本使用可选 token,检查 URL 和 HTTP 响应,并约定所有 completion handler 都异步回到主线程。

import Foundation

enum APIError: Error {
    case invalidURL
    case invalidResponse
    case httpStatus(Int)
}

final class APIManager {
    static let shared = APIManager()

    private init() {}

    typealias Completion = (Result<Data, Error>) -> Void

    func get(
        url: String,
        token: String? = nil,
        completion: @escaping Completion
    ) {
        let finish: Completion = { result in
            DispatchQueue.main.async {
                completion(result)
            }
        }

        guard let requestURL = URL(string: url),
              let scheme = requestURL.scheme?.lowercased(),
              ["http", "https"].contains(scheme),
              requestURL.host != nil else {
            finish(.failure(APIError.invalidURL))
            return
        }

        var request = URLRequest(url: requestURL)
        request.httpMethod = "GET"
        request.setValue(
            "application/json",
            forHTTPHeaderField: "Accept"
        )

        if let token = token, !token.isEmpty {
            request.setValue(
                "Bearer \(token)",
                forHTTPHeaderField: "Authorization"
            )
        }

        URLSession.shared.dataTask(with: request) {
            data, response, error in

            if let error = error {
                finish(.failure(error))
                return
            }

            guard let response = response as? HTTPURLResponse else {
                finish(.failure(APIError.invalidResponse))
                return
            }

            guard (200..<300).contains(response.statusCode) else {
                finish(.failure(
                    APIError.httpStatus(response.statusCode)
                ))
                return
            }

            finish(.success(data ?? Data()))
        }.resume()
    }
}

没有响应体的成功响应会返回空 Data,因此不会仅因为 204 等成功响应没有响应体就将其判为失败。如果接口明确要求返回 JSON,应在后续解析时检查响应内容。

示例只将失败状态码传给调用方。如果业务需要读取服务端错误码、错误消息或响应头,可以扩展错误类型或返回类型,保留这些信息。

如何调用?

公开接口不传 token:

APIManager.shared.get(
    url: "https://example.com/public"
) { result in
    switch result {
    case .success(let data):
        print("Received \(data.count) bytes")
    case .failure(let error):
        print("Request failed: \(error)")
    }
}

认证接口传入实际 token:

func loadProfile(token: String) {
    APIManager.shared.get(
        url: "https://example.com/profile",
        token: token
    ) { result in
        switch result {
        case .success(let data):
            print("Received \(data.count) bytes")
        case .failure(let error):
            print("Request failed: \(error)")
        }
    }
}

这些 URL 仅用于展示调用方式,需要替换成实际接口地址。

token 和线程还需要注意什么?

  • 可选 token 适合这个通用请求方法。对于必须登录的接口,业务层可以提前拒绝缺少 token 的调用,或通过专门的认证接口方法要求传入 token。没有 token 时不添加请求头,并不意味着接口允许匿名访问。
  • 本示例使用 Bearer 认证格式。只有服务端采用该格式时,才发送 Authorization: Bearer <token>;其他认证方式应遵循接口约定。
  • Accept 表达期望的响应格式,Content-Type 描述发送的请求体格式。原代码没有 GET 请求体,通常不需要声明 JSON Content-Type。
  • 示例统一在主线程回调,便于调用方更新 UI。较大的 JSON 解码或其他耗时处理应安排在后台,避免直接放进这里的 completion handler。
  • 如果 token 需要持久化,iOS 中可使用 Keychain 等适合保存凭证的机制。发送认证请求应使用 HTTPS,也不要把 token 写入日志。

如果以后需要支持更多 HTTP 方法,可以抽出接收 URLRequest 的公共发送方法,让 GET、POST 等方法分别组装请求。当前只有 GET 时,可以先保持接口简单。

备注:内容仅供参考。