URLSession 的三件套
iOS 原生网络就是 URLSession。它由三层构成:session(会话,管理连接与配置)、configuration(会话配置,决定缓存、超时、蜂窝开关)、task(具体的一次请求任务)。大多数情况用共享会话就够,需要精细控制时再自定义 configuration。
网络层的目标:让上层只管"要什么数据",缓存、超时、重试这些细节都藏在下面。
这篇从发请求讲到缓存策略,把 iOS 网络层搭得既快又省。
异步发起一个请求
配合 Swift 并发,发请求就是一行 await。URLSession.shared.data(from:) 同时拿到数据与响应。
func fetch() async throws -> [Item] {
let url = URL(string: "https://api.example.com/items")!
let (data, resp) = try await URLSession.shared.data(from: url)
guard let http = resp as? HTTPURLResponse,
http.statusCode == 200 else { throw URLError(.badServerResponse) }
return try JSONDecoder().decode([Item].self, from: data)
}
先检查状态码再解码,是网络层最基本的健壮性。
配置请求:方法与头
GET 之外要用 URLRequest 自定义:设 httpMethod、加请求头、放 body。POST 上传 JSON 时记得设 Content-Type。
- httpMethodGET / POST / PUT / DELETE。
- setValue加 Authorization、Content-Type 头。
- httpBody放序列化后的 JSON。
- timeoutInterval设合理超时,别等太久。
系统的 URL 缓存
URLSession 自带一个内存加磁盘的 URLCache,默认会遵守服务端返回的缓存头(Cache-Control)。也就是说,服务端说这个资源能缓存一分钟,系统就会在这分钟里直接给你缓存结果,不再请求网络。
let config = URLSessionConfiguration.default
config.urlCache = URLCache(
memoryCapacity: 8 * 1024 * 1024,
diskCapacity: 50 * 1024 * 1024)
config.requestCachePolicy = .useProtocolCachePolicy
用对缓存头,很多"第二次打开快"的需求系统就帮你做了。
自定义缓存策略
服务端不给缓存头时,就得自己决定。常用策略:有网就刷新、同时把旧数据先展示;无网就读本地缓存。把"网络结果"和"本地缓存"分成两层,界面永远先有内容。
缓存键通常用请求 URL 加参数,注意区分可缓存与不可缓存的请求(POST 一般不缓存)。
离线与失败兜底
用户没网时,不该白屏。约定一个状态:有缓存先展示缓存并标"离线",网络恢复再静默刷新。请求失败分三类处理——可重试的(超时)退避重试,不可重试的(404)直接提示,鉴权过期的跳登录。
- 弱网别一直转圈,给超时上限。
- 重试用指数退避,别瞬间狂打服务器。
- 敏感请求加 idempotency 标记,避免重复扣款。
实战:带缓存的请求层
封装一个方法:先读缓存,再发网络,成功后写回缓存。
func items() async throws -> [Item] {
if let cached: [Item] = cache.get(for: .items) { return cached }
let data = try await loadRemote()
cache.set(data, for: .items)
return data
}
第一次走网络并写缓存,之后命中缓存秒开。配合上面的离线策略,弱网也有内容。把缓存、超时、重试都收在这一层,界面只面对一个会返回数据的方法,网络层就成了。