iOS错误码详解与Alamofire错误处理指南

637 阅读8分钟

目录

  1. iOS错误码概述
  2. NSURLErrorDomain错误码
  3. HTTP状态码
  4. SSL错误码
  5. 其他常见错误域
  6. Alamofire错误处理机制
  7. Alamofire错误处理最佳实践
  8. 示例代码
  9. 参考资料

iOS错误码概述

在iOS开发中,错误信息(NSError)由错误域(Error Domain)和错误码(Error Code)组成。错误域用于区分不同类型的错误,而错误码则提供具体的错误信息。

错误域(Error Domain)

iOS中常见的错误域包括:

错误域描述
NSURLErrorDomainHTTP层错误,一般为NSURLConnection、NSURLSession返回的错误
NSCocoaErrorDomainCocoa框架相关错误
NSPOSIXErrorDomainPOSIX相关错误
NSOSStatusErrorDomain操作系统状态错误
NSMachErrorDomainMach内核相关错误

错误结构

iOS中的错误通常包含三个主要组成部分:

  1. Domain(域):标识错误的类别
  2. Code(代码):特定域内的错误代码
  3. UserInfo(用户信息):包含错误的详细信息,如本地化描述
let error = NSError(
    domain: NSURLErrorDomain,
    code: -1001,
    userInfo: [NSLocalizedDescriptionKey: "请求超时"]
)

NSURLErrorDomain错误码

NSURLErrorDomain是网络请求中最常见的错误域,包含了HTTP请求过程中可能遇到的各种错误。

常规错误码

错误码定义错误码描述
NSURLErrorUnknown-1未知错误
NSURLErrorCancelled-999请求被取消
NSURLErrorBadURL-1000URL非法
NSURLErrorTimedOut-1001请求超时
NSURLErrorUnsupportedURL-1002不支持的URL格式
NSURLErrorCannotFindHost-1003找不到主机
NSURLErrorCannotConnectToHost-1004无法连接到主机
NSURLErrorNetworkConnectionLost-1005网络连接中断
NSURLErrorDNSLookupFailed-1006DNS查找失败
NSURLErrorHTTPTooManyRedirects-1007HTTP重定向过多
NSURLErrorResourceUnavailable-1008资源不可用
NSURLErrorNotConnectedToInternet-1009未连接到互联网
NSURLErrorRedirectToNonExistentLocation-1010重定向到不存在的位置
NSURLErrorBadServerResponse-1011服务器响应异常
NSURLErrorUserCancelledAuthentication-1012用户取消认证
NSURLErrorUserAuthenticationRequired-1013需要用户认证
NSURLErrorZeroByteResource-1014零字节资源
NSURLErrorCannotDecodeRawData-1015无法解码原始数据
NSURLErrorCannotDecodeContentData-1016无法解码内容数据
NSURLErrorCannotParseResponse-1017无法解析响应
NSURLErrorInternationalRoamingOff-1018国际漫游关闭
NSURLErrorCallIsActive-1019通话活跃中
NSURLErrorDataNotAllowed-1020数据不被允许
NSURLErrorRequestBodyStreamExhausted-1021请求体流已耗尽

SSL错误码

SSL/TLS相关的错误码,通常在使用HTTPS协议时可能遇到:

错误码定义错误码描述
NSURLErrorSecureConnectionFailed-1200安全连接失败
NSURLErrorServerCertificateHasBadDate-1201服务器证书日期无效
NSURLErrorServerCertificateUntrusted-1202服务器证书不被信任
NSURLErrorServerCertificateHasUnknownRoot-1203服务器证书有未知的根证书
NSURLErrorServerCertificateNotYetValid-1204服务器证书尚未生效
NSURLErrorClientCertificateRejected-1205客户端证书被拒绝
NSURLErrorClientCertificateRequired-1206需要客户端证书
NSURLErrorCannotLoadFromNetwork-1207无法从网络加载

HTTP状态码

HTTP状态码是服务器对请求的响应状态,分为五类:

1xx - 信息性状态码

状态码描述
100继续
101切换协议
102处理中

2xx - 成功状态码

状态码描述
200成功
201已创建
202已接受
203非权威信息
204无内容
205重置内容
206部分内容

3xx - 重定向状态码

状态码描述
300多种选择
301永久移动
302临时移动
303查看其他位置
304未修改
305使用代理
307临时重定向
308永久重定向

4xx - 客户端错误状态码

状态码描述
400错误请求
401未授权
402需要付款
403禁止访问
404未找到
405方法不允许
406不可接受
407需要代理认证
408请求超时
409冲突
410已删除
411需要有效长度
412前提条件失败
413请求实体过大
414请求URI过长
415不支持的媒体类型
416请求范围不符合要求
417预期失败
422无法处理的实体
429请求过多

5xx - 服务器错误状态码

状态码描述
500服务器内部错误
501未实现
502错误网关
503服务不可用
504网关超时
505HTTP版本不支持
507存储空间不足
511需要网络认证

其他常见错误域

除了NSURLErrorDomain外,iOS开发中还会遇到其他错误域:

NSCocoaErrorDomain

Cocoa框架相关错误,如文件操作、数据解析等。

NSPOSIXErrorDomain

POSIX标准相关错误,通常与底层系统调用相关。

自定义错误域

开发者可以创建自定义错误域来处理应用特定的错误情况:

enum CustomError: Error {
    case networkError(reason: String)
    case validationError(reason: String)
    case databaseError(reason: String)
}

Alamofire错误处理机制

Alamofire是Swift中最受欢迎的网络请求框架之一,它提供了强大的错误处理机制。

AFError枚举

Alamofire通过定义一个遵循Swift标准Error协议的枚举类型AFError来处理各种网络请求中可能出现的错误:

public enum AFError: Error {
    // 参数编码失败的原因
    public enum ParameterEncodingFailureReason {
        case missingURL
        case jsonEncodingFailed(error: Error)
        case propertyListEncodingFailed(error: Error)
    }
    
    // 多部分编码失败的原因
    public enum MultipartEncodingFailureReason {
        case bodyPartURLInvalid(url: URL)
        case bodyPartFilenameInvalid(in: URL)
        case bodyPartFileNotReachable(at: URL)
        // 更多情况...
    }
    
    // 响应验证失败的原因
    public enum ResponseValidationFailureReason {
        case dataFileNil
        case dataFileReadFailed(at: URL)
        case missingContentType(acceptableContentTypes: [String])
        case unacceptableContentType(acceptableContentTypes: [String], responseContentType: String)
        case unacceptableStatusCode(code: Int)
    }
    
    // 响应序列化失败的原因
    public enum ResponseSerializationFailureReason {
        case inputDataNil
        case inputDataNilOrZeroLength
        case inputFileNil
        case inputFileReadFailed(at: URL)
        case stringSerializationFailed(encoding: String.Encoding)
        case jsonSerializationFailed(error: Error)
        case propertyListSerializationFailed(error: Error)
    }
    
    // 主要错误类型
    case invalidURL(url: URLConvertible)
    case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
    case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
    case responseValidationFailed(reason: ResponseValidationFailureReason)
    case responseSerializationFailed(reason: ResponseSerializationFailureReason)
}

错误处理扩展

Alamofire对AFError进行了扩展,增加了便捷的判断方法和属性:

extension AFError {
    // 判断是否是无效URL错误
    public var isInvalidURLError: Bool {
        if case .invalidURL = self { return true }
        return false
    }
    
    // 更多判断方法...
}

Alamofire错误处理最佳实践

1. 基本错误处理

Alamofire.request("https://api.example.com/data").responseJSON { response in
    switch response.result {
    case .success(let value):
        print("请求成功: \(value)")
    case .failure(let error):
        print("请求失败: \(error)")
        
        // 可以根据错误类型进行不同处理
        if let afError = error as? AFError {
            switch afError {
            case .invalidURL(let url):
                print("无效的URL: \(url)")
            case .parameterEncodingFailed(let reason):
                print("参数编码失败: \(reason)")
            // 处理其他错误类型...
            }
        }
    }
}

2. 请求验证

Alamofire.request("https://api.example.com/data")
    .validate(statusCode: 200..<300) // 验证状态码
    .validate(contentType: ["application/json"]) // 验证内容类型
    .responseJSON { response in
        switch response.result {
        case .success(let value):
            print("请求成功: \(value)")
        case .failure(let error):
            print("请求失败: \(error)")
        }
    }

3. 重试机制

class RetryPolicy: RequestRetrier {
    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
        if let response = request.task?.response as? HTTPURLResponse, response.statusCode >= 500 {
            // 服务器错误,重试
            completion(true, 1.0) // 1秒后重试
        } else {
            completion(false, 0.0) // 不重试
        }
    }
}

// 使用重试策略
let sessionManager = SessionManager()
sessionManager.retrier = RetryPolicy()

4. 错误处理分层

将网络错误、业务逻辑错误和UI展示错误分开处理:

// 网络层错误处理
func handleNetworkError(_ error: Error) {
    if let afError = error as? AFError {
        // 处理Alamofire特定错误
    } else if let urlError = error as? URLError {
        // 处理URLError
    } else {
        // 处理其他错误
    }
}

// 业务逻辑层错误处理
func handleBusinessError(_ error: Error) {
    // 处理业务逻辑错误
}

// UI层错误展示
func showErrorToUser(_ error: Error) {
    // 向用户展示友好的错误信息
}

5. 全局错误处理器

创建一个全局错误处理器,统一处理所有网络请求错误:

class NetworkErrorHandler {
    static let shared = NetworkErrorHandler()
    
    func handle(_ error: Error) {
        // 日志记录
        logError(error)
        
        // 错误分类处理
        if let afError = error as? AFError {
            handleAFError(afError)
        } else if let urlError = error as? URLError {
            handleURLError(urlError)
        } else {
            handleGenericError(error)
        }
    }
    
    private func handleAFError(_ error: AFError) {
        // 处理Alamofire特定错误
    }
    
    private func handleURLError(_ error: URLError) {
        // 处理URLError
    }
    
    private func handleGenericError(_ error: Error) {
        // 处理其他错误
    }
    
    private func logError(_ error: Error) {
        // 记录错误日志
    }
}

示例代码

完整的网络请求与错误处理示例

import Alamofire

class NetworkManager {
    static let shared = NetworkManager()
    
    func fetchData(from url: String, completion: @escaping (Result<Any, Error>) -> Void) {
        Alamofire.request(url)
            .validate(statusCode: 200..<300)
            .validate(contentType: ["application/json"])
            .responseJSON { response in
                switch response.result {
                case .success(let value):
                    completion(.success(value))
                case .failure(let error):
                    self.handleError(error)
                    completion(.failure(error))
                }
            }
    }
    
    private func handleError(_ error: Error) {
        if let afError = error as? AFError {
            switch afError {
            case .invalidURL(let url):
                print("无效的URL: \(url)")
            case .parameterEncodingFailed(let reason):
                print("参数编码失败: \(reason)")
            case .multipartEncodingFailed(let reason):
                print("多部分编码失败: \(reason)")
            case .responseValidationFailed(let reason):
                switch reason {
                case .unacceptableStatusCode(let code):
                    print("不可接受的状态码: \(code)")
                case .unacceptableContentType(let acceptableTypes, let responseType):
                    print("不可接受的内容类型: \(responseType), 可接受的类型: \(acceptableTypes)")
                default:
                    print("响应验证失败: \(reason)")
                }
            case .responseSerializationFailed(let reason):
                print("响应序列化失败: \(reason)")
            }
        } else if let urlError = error as? URLError {
            switch urlError.code {
            case .notConnectedToInternet:
                print("未连接到互联网")
            case .timedOut:
                print("请求超时")
            default:
                print("URL错误: \(urlError.localizedDescription)")
            }
        } else {
            print("未知错误: \(error.localizedDescription)")
        }
    }
}

// 使用示例
NetworkManager.shared.fetchData(from: "https://api.example.com/data") { result in
    switch result {
    case .success(let data):
        print("获取数据成功: \(data)")
    case .failure(let error):
        print("获取数据失败: \(error.localizedDescription)")
    }
}

自定义错误与Alamofire集成

// 自定义业务错误
enum BusinessError: Error {
    case invalidData
    case unauthorized
    case serverError(code: Int, message: String)
    case networkError(underlyingError: Error)
}

// 扩展BusinessError提供本地化描述
extension BusinessError: LocalizedError {
    var errorDescription: String? {
        switch self {
        case .invalidData:
            return "数据无效"
        case .unauthorized:
            return "未授权访问"
        case .serverError(_, let message):
            return "服务器错误: \(message)"
        case .networkError(let error):
            return "网络错误: \(error.localizedDescription)"
        }
    }
}

// 网络请求与自定义错误处理
func fetchUserProfile(userId: String, completion: @escaping (Result<UserProfile, BusinessError>) -> Void) {
    let url = "https://api.example.com/users/\(userId)"
    
    Alamofire.request(url)
        .validate()
        .responseJSON { response in
            switch response.result {
            case .success(let value):
                guard let json = value as? [String: Any] else {
                    completion(.failure(.invalidData))
                    return
                }
                
                // 检查业务逻辑错误
                if let errorCode = json["errorCode"] as? Int {
                    switch errorCode {
                    case 401:
                        completion(.failure(.unauthorized))
                    case let code where code >= 500:
                        let message = json["errorMessage"] as? String ?? "未知服务器错误"
                        completion(.failure(.serverError(code: code, message: message)))
                    default:
                        break
                    }
                    return
                }
                
                // 解析数据
                do {
                    let jsonData = try JSONSerialization.data(withJSONObject: json, options: [])
                    let userProfile = try JSONDecoder().decode(UserProfile.self, from: jsonData)
                    completion(.success(userProfile))
                } catch {
                    completion(.failure(.invalidData))
                }
                
            case .failure(let error):
                completion(.failure(.networkError(underlyingError: error)))
            }
        }
}

参考资料

  1. Apple Developer Documentation - NSError
  2. Apple Developer Documentation - URL Loading System
  3. Alamofire GitHub Repository
  4. HTTP Status Codes
  5. Swift Error Handling