如何在不使用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
相关产品推荐
相关产品推荐

