You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS App离线缓存max-stale失效:超期仍返回缓存数据

iOS NSURLRequest 缓存策略问题:max-stale 限制不生效

需求背景

设备在线时,每次请求直接发起网络请求;设备离线时,仅使用最长4小时内的缓存响应。

离线场景下的处理代码:

if isOffline && request.isOfflineCompatible {
    urlRequest.cachePolicy = .returnCacheDataDontLoad
    urlRequest.setValue("max-stale=14400", forHTTPHeaderField: "cache-control")
} else {
    // 恢复请求创建时设置的缓存策略,默认值为 useProtocolCachePolicy
    urlRequest.cachePolicy = request.cachePolicy
}

问题描述

无论设置的max-stale值为多少(测试时设为120秒),超过该时长后离线请求仍会返回成功(预期应返回失败),相同逻辑在Android平台运行正常。

离线请求对象信息

  • url : 保密API地址
  • cachePolicy : 3(对应.returnCacheDataDontLoad)
  • timeoutInterval : 30.0
  • mainDocumentURL : nil
  • networkServiceType : __C.NSURLRequestNetworkServiceType
  • allowsCellularAccess : true
  • httpMethod : "GET"
  • allHTTPHeaderFields :
    • key : "Cache-Control",value : "max-stale=120"(测试用短时长)
    • key : "Content-Type",value : "application/json"
    • key : "APIKey",value : "abcdxyz"
  • httpBody : nil
  • httpBodyStream : nil
  • httpShouldHandleCookies : true
  • httpShouldUsePipelining : false

对应响应信息

return (<NSHTTPURLResponse: 0x281dc61a0> { URL: 保密地址 } { Status Code: 200, Headers {
"Access-Control-Allow-Origin" =     (
"*"
);
"Cache-Control" =     (
"max-age=30"
);
"Content-Encoding" =     (
gzip
);
"Content-Length" =     (
9797
);
"Content-Type" =     (
"application/json"
);
Date =     (
"Sun, 16 Apr 2023 18:33:23 GMT"
);
Etag =     (
0x8DB33C6C0E756AD
);
Expires =     (
"Sun, 16 Apr 2023 18:33:53 GMT"
);
"Last-Modified" =     (
"Sun, 02 Apr 2023 22:08:13 GMT"
);
Vary =     (
"Accept-Encoding"
);
"content-md5" =     (
"ozV+j+j89923XIxSCSjFoA=="
);
"request-context" =     (
"appId=cid-v1:79144720-54fb-43e6-964e-37c4f997a8ec"
);
"x-ms-blob-type" =     (
BlockBlob
);
"x-ms-lease-status" =     (
unlocked
);
"x-ms-request-id" =     (
"154aae09-301e-004e-201f-6e69d9000000"
);
"x-ms-version" =     (
"2009-09-19"
); } }
)

问题分析与解决方案

iOS的NSURLCache对max-stale的处理逻辑和Android的HttpURLCache存在差异:当设置cachePolicy为.returnCacheDataDontLoad时,系统会直接返回缓存数据,忽略Cache-Control头中的max-stale限制。

可行解决方法

  1. 手动校验缓存有效期
    获取缓存响应后,自行计算缓存是否超出max-stale限制:
  • 从响应头的Date、Expires或Cache-Control的max-age字段,计算缓存的基础过期时间。
  • 叠加max-stale允许的时长,判断当前时间是否超出该范围,超出则手动返回失败。
  1. 自定义NSURLCache
    继承NSURLCache并重写cachedResponseForRequest:方法,在该方法中加入max-stale校验逻辑,不符合条件时返回nil,触发请求失败。

  2. 替换缓存策略
    改用.returnCacheDataElseLoad作为缓存策略,同时在离线状态下拦截网络请求,强制使用缓存。此方式需要额外处理离线状态的网络拦截逻辑。

示例代码(手动校验方式)

// 假设已获取到缓存响应response和对应数据data
guard let httpResponse = response as? HTTPURLResponse,
      let dateStr = httpResponse.allHeaderFields["Date"] as? String,
      let date = DateFormatter.rfc1123DateFormatter.date(from: dateStr),
      let cacheControl = httpResponse.allHeaderFields["Cache-Control"] as? String else {
    // 无法校验,按失败处理
    completion(.failure(CacheError.invalidResponse))
    return
}

// 提取响应头中的max-age值
let maxAgeRegex = try! NSRegularExpression(pattern: "max-age=(\\d+)")
let maxAgeMatch = maxAgeRegex.firstMatch(in: cacheControl, range: NSRange(cacheControl.startIndex..., in: cacheControl))
let maxAge = maxAgeMatch.flatMap {
    String(cacheControl[Range($0.range(at: 1), in: cacheControl)!])
}.flatMap(Int.init) ?? 0

// 读取请求头中的max-stale值(或直接使用预设的14400秒)
let maxStale = urlRequest.allHTTPHeaderFields?["Cache-Control"]?.split(separator: "=").last.flatMap(Int.init) ?? 14400
// 计算允许的最大过期时间
let allowedExpirationTime = date.addingTimeInterval(TimeInterval(maxAge + maxStale))

if Date() > allowedExpirationTime {
    // 缓存已超出允许的过期时长,返回失败
    completion(.failure(CacheError.staleCache))
} else {
    // 缓存有效,返回数据
    completion(.success(data))
}

// 辅助工具:RFC1123日期格式化器
extension DateFormatter {
    static let rfc1123DateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
        formatter.timeZone = TimeZone(identifier: "GMT")
        return formatter
    }()
}

// 自定义缓存错误类型
enum CacheError: Error {
    case invalidResponse
    case staleCache
}

内容的提问来源于stack exchange,提问作者7vikram7

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 22:48:10