目录
iOS错误码概述
在iOS开发中,错误信息(NSError)由错误域(Error Domain)和错误码(Error Code)组成。错误域用于区分不同类型的错误,而错误码则提供具体的错误信息。
错误域(Error Domain)
iOS中常见的错误域包括:
| 错误域 | 描述 |
|---|---|
| NSURLErrorDomain | HTTP层错误,一般为NSURLConnection、NSURLSession返回的错误 |
| NSCocoaErrorDomain | Cocoa框架相关错误 |
| NSPOSIXErrorDomain | POSIX相关错误 |
| NSOSStatusErrorDomain | 操作系统状态错误 |
| NSMachErrorDomain | Mach内核相关错误 |
错误结构
iOS中的错误通常包含三个主要组成部分:
- Domain(域):标识错误的类别
- Code(代码):特定域内的错误代码
- UserInfo(用户信息):包含错误的详细信息,如本地化描述
let error = NSError(
domain: NSURLErrorDomain,
code: -1001,
userInfo: [NSLocalizedDescriptionKey: "请求超时"]
)
NSURLErrorDomain错误码
NSURLErrorDomain是网络请求中最常见的错误域,包含了HTTP请求过程中可能遇到的各种错误。
常规错误码
| 错误码定义 | 错误码 | 描述 |
|---|---|---|
| NSURLErrorUnknown | -1 | 未知错误 |
| NSURLErrorCancelled | -999 | 请求被取消 |
| NSURLErrorBadURL | -1000 | URL非法 |
| NSURLErrorTimedOut | -1001 | 请求超时 |
| NSURLErrorUnsupportedURL | -1002 | 不支持的URL格式 |
| NSURLErrorCannotFindHost | -1003 | 找不到主机 |
| NSURLErrorCannotConnectToHost | -1004 | 无法连接到主机 |
| NSURLErrorNetworkConnectionLost | -1005 | 网络连接中断 |
| NSURLErrorDNSLookupFailed | -1006 | DNS查找失败 |
| NSURLErrorHTTPTooManyRedirects | -1007 | HTTP重定向过多 |
| 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 | 网关超时 |
| 505 | HTTP版本不支持 |
| 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)))
}
}
}