iOS 网络层分层与错误映射

举报
shenlan9755 发表于 2026/09/15 09:20:57 2026/09/15
【摘要】 iOS 网络层分层与错误映射网络层不仅负责发出请求,还承担参数编码、响应解析、身份处理、错误分类和取消传播等职责。如果页面直接操作底层请求对象,协议变化会扩散到整个应用,测试也只能依赖真实环境。通过分层和稳定的错误模型,可以让业务代码只关注“请求什么”和“结果意味着什么”。 一、划分网络职责一个可维护的网络链路通常包含:请求描述:方法、参数、身份要求和响应类型。传输层:执行请求,返回原始数...

iOS 网络层分层与错误映射

网络层不仅负责发出请求,还承担参数编码、响应解析、身份处理、错误分类和取消传播等职责。如果页面直接操作底层请求对象,协议变化会扩散到整个应用,测试也只能依赖真实环境。通过分层和稳定的错误模型,可以让业务代码只关注“请求什么”和“结果意味着什么”。

一、划分网络职责

一个可维护的网络链路通常包含:

  • 请求描述:方法、参数、身份要求和响应类型。
  • 传输层:执行请求,返回原始数据和响应信息。
  • 解析层:把数据转换为协议模型。
  • 仓库层:组合接口与本地数据,映射为业务结果。
  • 页面模型:把业务结果转换为界面状态。

每层只暴露上层真正需要的信息。页面不需要知道状态码细节,传输层也不应该决定提示文案。

二、用类型描述请求

protocol Endpoint {
    associatedtype Response: Decodable

    var method: RequestMethod { get }
    var parameters: [String: String] { get }
    var requiresAuthentication: Bool { get }
}

struct OrderListEndpoint: Endpoint {
    typealias Response = OrderListResponse

    let page: Int
    let pageSize: Int

    var method: RequestMethod { .get }
    var parameters: [String: String] {
        [
            "page": String(page),
            "pageSize": String(pageSize)
        ]
    }
    var requiresAuthentication: Bool { true }
}

具体请求位置可以由项目统一配置和路由表管理,避免在业务页面散落字符串。示例只表达参数和响应类型,不重复实现已有网络基础设施。

三、保持传输接口稳定

protocol NetworkClient {
    func send<E: Endpoint>(_ endpoint: E) async throws -> E.Response
}

调用方只关心解码后的响应。具体会话配置、请求构造和底层回调桥接都封装在实现中。测试时可以替换为可控客户端,而不需要真正发起通信。

四、区分不同错误层级

错误至少可以分为:

  • 传输错误:连接中断、超时、任务取消。
  • 协议错误:响应格式或状态不符合约定。
  • 解析错误:字段类型与模型不匹配。
  • 业务错误:未登录、余额不足、操作被拒绝。
enum NetworkError: Error, Equatable {
    case unavailable
    case timeout
    case invalidResponse
    case decodingFailed
    case cancelled
}

enum OrderError: Error, Equatable {
    case loginRequired
    case permissionDenied
    case rejected(reason: String)
    case temporarilyUnavailable
}

底层错误不应原样穿透到用户界面。仓库层根据协议中明确的错误码映射业务错误,不必为未约定字段添加猜测性兜底。

五、正确传播取消

页面离开或新搜索到来时,上层任务取消应传递到底层请求。捕获所有错误并统一映射,会把取消误判为网络失败。

func loadOrders() async throws -> [Order] {
    do {
        let response = try await client.send(
            OrderListEndpoint(page: 1, pageSize: 20)
        )
        try Task.checkCancellation()
        return response.items.map(orderMapper.toDomain)
    } catch is CancellationError {
        throw CancellationError()
    } catch let error as NetworkError {
        throw mapNetworkError(error)
    }
}

底层桥接旧回调接口时,也要在任务取消后调用请求自身的取消方法,避免只是不接收结果但资源仍被占用。

六、统一解码策略

日期格式、键名转换和空值规则应在统一解码器中配置。各业务自行创建解码器容易造成同一个字段在不同模块表现不一致。

模型应忠实表达接口契约。字段被协议定义为必填时,就使用非可选类型,让解析失败尽早暴露;只有确实允许缺失或为空的字段才使用可选类型,不要把所有字段都做成可选来掩盖协议错误。

七、身份刷新与请求重放

身份过期时,可以由网络基础层协调一次刷新,等待中的请求共享同一个刷新任务。刷新成功后,只重放符合安全和幂等要求的请求。

提交订单、支付确认等操作不能无条件自动重放,否则可能产生重复业务结果。是否允许重试必须成为请求描述的一部分,并遵守服务端幂等约定。

刷新失败后,应统一清理身份状态并通知上层进入登录流程,避免多个页面同时弹出重复提示。

八、设计缓存边界

网络层可以处理协议级缓存,仓库层处理业务数据缓存。用户资料、实时行情、静态配置的有效期完全不同,不能使用一套统一时间作为万能策略。

缓存命中后是否仍刷新、失败时是否展示旧数据,都应由具体业务决定。旧数据如果可能误导用户,就不应为了“页面有内容”而盲目展示。

九、保护日志中的数据

调试日志不应完整输出身份信息、请求体和响应体。统一网络组件可以对允许记录的字段做白名单或脱敏处理,并在正式构建中遵循项目日志级别。

错误日志应保留定位所需的请求标识、业务码和阶段信息,但避免记录用户敏感数据。

十、测试网络层

使用客户端替身可以稳定覆盖:

  • 正常响应与空列表。
  • 传输中断和超时映射。
  • 必填字段缺失时的解析失败。
  • 业务拒绝码的映射。
  • 页面取消后是否停止处理。
  • 身份刷新并发时是否只执行一次。
  • 不可重放请求是否被错误重试。

总结

清晰的 iOS 网络层应把请求描述、传输、解析和业务映射分开。使用强类型契约、保留取消语义、按业务决定缓存与重放,并集中保护日志数据,可以让接口变化和异常处理停留在合理边界内,而不是扩散到每个页面。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。