You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在不使用Alamofire等第三方库时减少网络请求代码冗余?

嘿,这个场景太熟悉了——要写70多个网络请求方法,重复的通用代码简直是维护噩梦!不用第三方库的话,我们完全可以用Swift原生的泛型+封装通用逻辑的方式来解决,既简洁又易维护,下面是具体的实现思路和代码示例:

核心思路:抽离通用网络请求流程

所有网络请求的通用步骤无非是:构建URL、创建请求、处理参数、发起请求、解析响应、回调结果。我们把这些重复逻辑封装成一个基础方法,让具体的业务请求只需要传入差异化的参数(比如接口路径、请求方法、参数、响应模型类型)即可。

第一步:封装通用网络请求基础方法

在你的ServerModel里先实现这个核心通用方法,把所有重复的逻辑都放在这里:

class ServerModel {
    // 自定义网络错误枚举,统一处理错误类型
    enum NetworkError: Error, LocalizedError {
        case invalidBaseURL
        case invalidStatusCode(Int)
        case noDataReturned
        case decodeFailed
        
        var errorDescription: String? {
            switch self {
            case .invalidBaseURL:
                return "无效的基础URL"
            case .invalidStatusCode(let code):
                return "请求失败,状态码:\(code)"
            case .noDataReturned:
                return "服务器未返回数据"
            case .decodeFailed:
                return "数据解析失败"
            }
        }
    }
    
    // HTTP方法枚举,避免硬写字符串
    enum HTTPMethod: String {
        case get = "GET"
        case post = "POST"
        case put = "PUT"
        case delete = "DELETE"
    }
    
    // MARK: - 通用网络请求方法
    private func performRequest<T: Decodable>(
        path: String,
        method: HTTPMethod,
        parameters: [String: Any]?,
        responseType: T.Type,
        onSuccess: @escaping (T) -> Void,
        onFailure: @escaping (Error) -> Void
    ) {
        // 1. 构建完整URL
        guard let baseURL = URL(string: "https://your-api-base-url.com") else {
            DispatchQueue.main.async { onFailure(NetworkError.invalidBaseURL) }
            return
        }
        let requestURL = baseURL.appendingPathComponent(path)
        
        // 2. 创建URLRequest
        var request = URLRequest(url: requestURL)
        request.httpMethod = method.rawValue
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        // 3. 处理请求参数(GET拼URL,POST放body)
        if let parameters = parameters {
            switch method {
            case .get:
                // GET请求:把参数拼到URL上
                var components = URLComponents(url: requestURL, resolvingAgainstBaseURL: false)
                components?.queryItems = parameters.map { URLQueryItem(name: $0.key, value: "\($0.value)") }
                guard let getURL = components?.url else {
                    DispatchQueue.main.async { onFailure(NetworkError.invalidBaseURL) }
                    return
                }
                request.url = getURL
            default:
                // POST/PUT/DELETE:把参数转成JSON放在body里
                do {
                    request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
                } catch {
                    DispatchQueue.main.async { onFailure(error) }
                    return
                }
            }
        }
        
        // 4. 发起请求并处理响应
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            // 先处理网络层错误
            if let error = error {
                DispatchQueue.main.async { onFailure(error) }
                return
            }
            
            // 验证响应状态码
            guard let httpResponse = response as? HTTPURLResponse else {
                DispatchQueue.main.async { onFailure(NetworkError.invalidStatusCode(0)) }
                return
            }
            guard (200...299).contains(httpResponse.statusCode) else {
                DispatchQueue.main.async { onFailure(NetworkError.invalidStatusCode(httpResponse.statusCode)) }
                return
            }
            
            // 验证返回数据
            guard let responseData = data else {
                DispatchQueue.main.async { onFailure(NetworkError.noDataReturned) }
                return
            }
            
            // 解析数据到指定模型
            do {
                let decodedData = try JSONDecoder().decode(T.self, from: responseData)
                DispatchQueue.main.async { onSuccess(decodedData) }
            } catch {
                DispatchQueue.main.async { onFailure(NetworkError.decodeFailed) }
            }
        }
        task.resume()
    }
}

第二步:实现具体业务请求方法

现在所有业务请求只需要调用上面的通用方法,传入差异化参数即可,完全不用重复通用逻辑:

extension ServerModel {
    // 登录接口示例
    func login(
        userName: String,
        password: String,
        onSuccess: @escaping (LoginResponse) -> Void,
        onFailure: @escaping (Error) -> Void
    ) {
        let params = [
            "userName": userName,
            "password": password
        ]
        
        performRequest(
            path: "/api/auth/login",
            method: .post,
            parameters: params,
            responseType: LoginResponse.self,
            onSuccess: onSuccess,
            onFailure: onFailure
        )
    }
    
    // 获取用户信息接口示例
    func getUserInfo(
        userId: String,
        onSuccess: @escaping (UserInfoResponse) -> Void,
        onFailure: @escaping (Error) -> Void
    ) {
        let params = ["userId": userId]
        
        performRequest(
            path: "/api/user/info",
            method: .get,
            parameters: params,
            responseType: UserInfoResponse.self,
            onSuccess: onSuccess,
            onFailure: onFailure
        )
    }
    
    // 剩下的70多个方法都可以按照这个模式写,每个方法只需要几行代码!
}

// 对应的响应模型示例(需遵循Decodable)
struct LoginResponse: Decodable {
    let token: String
    let userId: String
    let expiresAt: TimeInterval
}

struct UserInfoResponse: Decodable {
    let name: String
    let email: String
    let avatarURL: String
    
    // 如果接口字段和模型属性名不一致,可以用CodingKey映射
    enum CodingKeys: String, CodingKey {
        case name, email
        case avatarURL = "avatar_url"
    }
}

额外优化建议

  • 把baseURL抽成配置项(比如放在Info.plist里),方便后续环境切换;
  • 如果所有接口都需要统一的请求头(比如token),可以在通用方法里统一添加,不用每个业务方法单独处理;
  • 如果接口有统一的响应格式(比如所有响应都包含code、message、data字段),可以封装一个BaseResponse<T>泛型模型,进一步简化解析逻辑。

内容的提问来源于stack exchange,提问作者AbaEesa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:11:38