swift之网络请求工具类(Alamofire+HandyJSON封装)async

使用 Alamofire + HandyJSON 封装(支持 async/await 和 JSON 字符串返回)

下面是一个结合 Alamofire 和 HandyJSON 的完整封装方案,支持 async/await 并可以返回 JSON 字符串或模型对象。

1. 基础封装

swift
 
复制
 
下载
import Alamofire
import HandyJSON

enum NetworkError: Error {
    case invalidURL
    case invalidResponse
    case statusCode(Int, String?)
    case decodingError(Error)
    case underlying(Error)
    case noData
    case jsonSerializationFailed
}

class NetworkManager {
    static let shared = NetworkManager()
    private let session: Session
    
    private init() {
        let configuration = URLSessionConfiguration.af.default
        configuration.timeoutIntervalForRequest = 30
        configuration.timeoutIntervalForResource = 60
        session = Session(configuration: configuration)
    }
    
    // MARK: - 基础请求方法
    private func request(
        _ url: URLConvertible,
        method: HTTPMethod = .get,
        parameters: Parameters? = nil,
        encoding: ParameterEncoding = JSONEncoding.default,
        headers: HTTPHeaders? = nil
    ) async throws -> Data {
        return try await withCheckedThrowingContinuation { continuation in
            session.request(
                url,
                method: method,
                parameters: parameters,
                encoding: encoding,
                headers: headers
            )
            .validate()
            .responseData { response in
                switch response.result {
                case .success(let data):
                    continuation.resume(returning: data)
                case .failure(let error):
                    if let afError = error.asAFError {
                        switch afError {
                        case .responseValidationFailed(let reason):
                            if case .unacceptableStatusCode(let code) = reason {
                                // 尝试解析错误信息
                                let errorMessage = try? JSONSerialization.jsonObject(with: response.data ?? Data(), options: []) as? [String: Any]
                                continuation.resume(throwing: NetworkError.statusCode(code, errorMessage?["message"] as? String))
                                return
                            }
                        default: break
                        }
                    }
                    continuation.resume(throwing: NetworkError.underlying(error))
                }
            }
        }
    }
    
    // MARK: - 公开方法
    
    /// 返回 HandyJSON 模型对象
    func requestModel<T: HandyJSON>(
        _ url: URLConvertible,
        method: HTTPMethod = .get,
        parameters: Parameters? = nil,
        encoding: ParameterEncoding = JSONEncoding.default,
        headers: HTTPHeaders? = nil
    ) async throws -> T {
        let data = try await request(url, method: method, parameters: parameters, encoding: encoding, headers: headers)
        
        guard let jsonString = String(data: data, encoding: .utf8) else {
            throw NetworkError.jsonSerializationFailed
        }
        
        guard let model = T.deserialize(from: jsonString) else {
            throw NetworkError.decodingError(NSError(domain: "HandyJSON", code: -1, userInfo: [NSLocalizedDescriptionKey: "模型解析失败"]))
        }
        
        return model
    }
    
    /// 返回 JSON 字符串
    func requestJSONString(
        _ url: URLConvertible,
        method: HTTPMethod = .get,
        parameters: Parameters? = nil,
        encoding: ParameterEncoding = JSONEncoding.default,
        headers: HTTPHeaders? = nil
    ) async throws -> String {
        let data = try await request(url, method: method, parameters: parameters, encoding: encoding, headers: headers)
        
        guard let jsonString = String(data: data, encoding: .utf8) else {
            throw NetworkError.jsonSerializationFailed
        }
        
        return jsonString
    }
    
    /// 返回原始 JSON 对象 (Any)
    func requestRawJSON(
        _ url: URLConvertible,
        method: HTTPMethod = .get,
        parameters: Parameters? = nil,
        encoding: ParameterEncoding = JSONEncoding.default,
        headers: HTTPHeaders? = nil
    ) async throws -> Any {
        let data = try await request(url, method: method, parameters: parameters, encoding: encoding, headers: headers)
        
        do {
            return try JSONSerialization.jsonObject(with: data, options: [])
        } catch {
            throw NetworkError.decodingError(error)
        }
    }
}

2. 高级功能扩展

swift
 
复制
 
下载
extension NetworkManager {
    // MARK: - 上传文件
    
    /// 上传文件并返回 HandyJSON 模型
    func uploadModel<T: HandyJSON>(
        _ data: Data,
        to url: URLConvertible,
        method: HTTPMethod = .post,
        headers: HTTPHeaders? = nil
    ) async throws -> T {
        return try await withCheckedThrowingContinuation { continuation in
            session.upload(data, to: url, method: method, headers: headers)
                .validate()
                .responseString { response in
                    switch response.result {
                    case .success(let jsonString):
                        guard let model = T.deserialize(from: jsonString) else {
                            continuation.resume(throwing: NetworkError.decodingError(NSError(domain: "HandyJSON", code: -1, userInfo: [NSLocalizedDescriptionKey: "模型解析失败"])))
                            return
                        }
                        continuation.resume(returning: model)
                    case .failure(let error):
                        continuation.resume(throwing: NetworkError.underlying(error))
                    }
                }
        }
    }
    
    // MARK: - 带进度回调的上传
    
    func uploadWithProgress<T: HandyJSON>(
        _ data: Data,
        to url: URLConvertible,
        method: HTTPMethod = .post,
        headers: HTTPHeaders? = nil,
        progressHandler: @escaping (Progress) -> Void
    ) async throws -> T {
        return try await withCheckedThrowingContinuation { continuation in
            session.upload(data, to: url, method: method, headers: headers)
                .uploadProgress(closure: progressHandler)
                .validate()
                .responseString { response in
                    switch response.result {
                    case .success(let jsonString):
                        guard let model = T.deserialize(from: jsonString) else {
                            continuation.resume(throwing: NetworkError.decodingError(NSError(domain: "HandyJSON", code: -1, userInfo: [NSLocalizedDescriptionKey: "模型解析失败"])))
                            return
                        }
                        continuation.resume(returning: model)
                    case .failure(let error):
                        continuation.resume(throwing: NetworkError.underlying(error))
                    }
                }
        }
    }
}

3. 使用示例

3.1 定义模型

swift
 
复制
 
下载
struct User: HandyJSON {
    var id: Int = 0
    var name: String = ""
    var email: String = ""
    
    // 可选:自定义映射关系
    mutating func mapping(mapper: HelpingMapper) {
        mapper <<< self.name <-- "username"
    }
}

struct ApiResponse<T: HandyJSON>: HandyJSON {
    var code: Int = 0
    var message: String = ""
    var data: T?
}

3.2 请求并返回模型

swift
 
复制
 
下载
// 获取用户信息
Task {
    do {
        let response: ApiResponse<User> = try await NetworkManager.shared.requestModel(
            "https://api.example.com/user/1",
            method: .get
        )
        
        if response.code == 200, let user = response.data {
            print("获取用户成功: \(user.name)")
        } else {
            print("请求失败: \(response.message)")
        }
    } catch {
        print("请求出错: \(error)")
    }
}

3.3 返回 JSON 字符串

swift
 
复制
 
下载
Task {
    do {
        let jsonString = try await NetworkManager.shared.requestJSONString(
            "https://api.example.com/user/1"
        )
        print("原始JSON字符串: \(jsonString)")
        
        // 可以手动解析
        if let user = User.deserialize(from: jsonString) {
            print("手动解析的用户: \(user.name)")
        }
    } catch {
        print("请求出错: \(error)")
    }
}

3.4 上传文件

swift
 
复制
 
下载
Task {
    guard let imageData = UIImage(named: "avatar")?.jpegData(compressionQuality: 0.8) else { return }
    
    do {
        let response: ApiResponse<String> = try await NetworkManager.shared.uploadModel(
            imageData,
            to: "https://api.example.com/upload",
            headers: ["Content-Type": "image/jpeg"]
        )
        
        if response.code == 200, let url = response.data {
            print("上传成功,文件URL: \(url)")
        } else {
            print("上传失败: \(response.message)")
        }
    } catch {
        print("上传出错: \(error)")
    }
}

4. 错误处理扩展

swift
 
复制
 
下载
extension NetworkError: LocalizedError {
    var errorDescription: String? {
        switch self {
        case .invalidURL:
            return "无效的URL地址"
        case .invalidResponse:
            return "无效的服务器响应"
        case .statusCode(let code, let message):
            return message ?? "服务器错误 (代码: \(code))"
        case .decodingError(let error):
            return "数据解析失败: \(error.localizedDescription)"
        case .underlying(let error):
            return "网络请求错误: \(error.localizedDescription)"
        case .noData:
            return "没有接收到数据"
        case .jsonSerializationFailed:
            return "JSON序列化失败"
        }
    }
}

// 使用示例
Task {
    do {
        let user: User = try await NetworkManager.shared.requestModel("https://api.example.com/user/1")
        // 处理用户数据...
    } catch let error as NetworkError {
        showAlert(title: "错误", message: error.localizedDescription)
    } catch {
        showAlert(title: "错误", message: error.localizedDescription)
    }
}

5. 高级配置

5.1 添加请求拦截器(如自动添加Token)

swift
 
复制
 
下载
class AuthInterceptor: RequestInterceptor {
    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
        var request = urlRequest
        if let token = AuthManager.shared.token {
            request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        }
        completion(.success(request))
    }
    
    func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
        // 可以在这里实现Token过期自动刷新逻辑
        completion(.doNotRetry)
    }
}

// 在NetworkManager初始化时配置
private init() {
    let configuration = URLSessionConfiguration.af.default
    configuration.timeoutIntervalForRequest = 30
    session = Session(
        configuration: configuration,
        interceptor: AuthInterceptor()
    )
}

5.2 日志记录

swift
 
复制
 
下载
class NetworkLogger: EventMonitor {
    func requestDidFinish(_ request: Request) {
        print("请求完成: \(request.description)")
    }
    
    func request<Value>(_ request: DataRequest, didParseResponse response: DataResponse<Value, AFError>) {
        if let data = response.data, let json = try? JSONSerialization.jsonObject(with: data, options: []) {
            print("响应数据: \(json)")
        }
    }
}

// 在NetworkManager初始化时添加
private init() {
    let configuration = URLSessionConfiguration.af.default
    configuration.timeoutIntervalForRequest = 30
    session = Session(
        configuration: configuration,
        interceptor: AuthInterceptor(),
        eventMonitors: [NetworkLogger()]
    )
}
posted @ 2025-07-01 20:46  哇哈爽  阅读(145)  评论(0)    收藏  举报