iOS 网络缓存:HTTP协议缓存、NSURLSession系统缓存、本地缓存与无感刷新

73 阅读13分钟

一、前言:90% iOS 项目的缓存乱象,全是认知错误

做 iOS 开发几乎没人能绕开网络缓存,但日常项目中,绝大多数开发者对缓存的认知只停留在「存个沙盒、下次读取」,导致线上层出不穷的缓存疑难问题:

  • 接口数据更新了,App 一直展示旧缓存,用户刷不出新内容
  • 断网白屏、弱网加载缓慢,体验极差,竞品却能秒开页面
  • 部分接口走缓存、部分接口不生效,缓存规则混乱无法统一管控
  • 缓存无限堆积,导致 App 体积越来越大、缓存垃圾无法清理
  • 手动写缓存逻辑冗余重复,每个业务都要单独判空、存数据、读数据
  • 想做「先展示缓存、再刷新最新数据」的无感刷新,始终实现不完美

很多人分不清:HTTP 协议缓存、NSURLSession 系统缓存、业务层本地缓存三者的区别,盲目混用导致缓存失效、脏数据、更新延迟。

本文结合多年一线项目优化经验,从零拆解 iOS 整套缓存体系:底层原理、系统缓存机制、协议缓存规则、自定义缓存封装、无感刷新架构、高频踩坑复盘,搭配全套可复用代码、业务场景案例、面试核心考点,彻底打通 iOS 网络缓存底层与工程落地。

二、核心认知:iOS 三层缓存架构(必懂分层逻辑)

iOS 网络缓存不是单一逻辑,是三层自上而下的完整体系,每层职责、优先级、适用场景完全不同,这是解决所有缓存问题的核心前提:

1. 三层缓存分层定义

  • 第一层:HTTP 协议缓存(服务端控制) :遵循 RFC 标准,通过 Response 响应头(Cache-Control、ETag、Last-Modified)控制缓存有效期、校验规则,是跨端统一的缓存标准
  • 第二层:NSURLSession 系统缓存(系统层) :基于 NSURLCache 实现,自动接管 HTTP 协议缓存,内存+磁盘双缓存,无需手动读写,NSURLSession/AFN 底层默认生效
  • 第三层:业务本地缓存(客户端自定义) :基于沙盒/YYCache/数据库实现的自定义缓存,完全脱离系统限制,可自由控制缓存粒度、过期时间、清理规则,适配复杂业务场景

2. 分层核心区别(实战关键)

HTTP缓存、系统缓存适合静态数据、稳定接口;自定义本地缓存适合动态业务数据、个性化页面、需要精准控权的场景,三者互补才能实现最优体验。

三、第一层:HTTP 协议缓存底层原理(服务端+客户端联动)

HTTP 缓存是所有网络缓存的基石,分为强缓存协商缓存两种机制,优先级:强缓存 > 协商缓存。无需客户端写代码,只要响应头配置正确,NSURLSession 会自动识别并执行。

1. 强缓存(无需请求服务器,直接读本地缓存)

强缓存生效时,客户端不会发起任何网络请求,直接读取本地缓存数据,状态码 200(from cache),速度极致最快。

依靠两个响应头控制:

  • Cache-Control(现代主流,优先级最高):max-age=3600 代表缓存有效期1小时
  • Expires(老旧兼容):绝对过期时间,受客户端时间篡改影响,现已逐步淘汰

实战案例1:首页静态文案、Banner图缓存

服务端配置响应头:Cache-Control: max-age=86400,24小时内重复请求直接读缓存,无需网络请求,大幅减少接口压力、提升页面打开速度。

强缓存致命缺点:有效期内,无论服务端数据如何更新,客户端永远读取旧数据,无法实时更新。

2. 协商缓存(询问服务器,按需更新)

强缓存过期后,不会直接请求新数据,而是发起轻量校验请求,询问服务器数据是否更新:

  • 数据未更新:返回 304 Not Modified,客户端复用本地缓存
  • 数据已更新:返回 200 + 最新数据,客户端更新本地缓存

协商缓存两组配对规则(自动触发):

  1. ETag / If-None-Match(优先级高、精准校验):服务端返回文件唯一指纹,客户端下次请求携带指纹,服务端比对指纹是否一致
  2. Last-Modified / If-Modified-Since(兼容方案):基于文件最后修改时间校验,精度秒级,无法识别秒内多次修改

3. 协议缓存完整执行链路(项目真实流程)

首次请求:客户端请求数据 → 服务端返回数据+缓存头 → 系统自动存入缓存

二次请求:优先判断强缓存是否有效 → 有效直接读取 → 失效走协商缓存 → 304复用缓存/200更新缓存

四、第二层:NSURLSession 系统缓存 NSURLCache 底层实战

iOS 中所有基于 NSURLSession 的网络请求(AFN、原生请求),默认由 NSURLCache 接管 HTTP 协议缓存,实现内存缓存+磁盘缓存双缓存机制,开发者无需手动存储。

1. NSURLCache 双缓存机制

  • 内存缓存:读写速度极快,App 运行期间常驻,重启 App 丢失
  • 磁盘缓存:持久化沙盒存储,重启 App、重启手机依然保留,过期自动失效

2. 系统默认缓存策略(NSURLRequestCachePolicy)

iOS 提供7种缓存策略,4种常用核心策略,适配绝大多数业务场景:

缓存策略核心逻辑适用场景
UseProtocolCachePolicy(默认)完全遵循HTTP协议,强缓存优先,过期走协商缓存常规接口、静态资源
ReloadIgnoringLocalCacheData忽略所有缓存,强制实时请求网络实时数据、支付、个人中心、敏感数据
ReturnCacheDataElseLoad优先读缓存,无缓存再请求网络离线可用页面、内容稳定性页面
ReturnCacheDataDontLoad只读缓存,绝不请求网络离线专属页面、历史记录

3. 全局缓存配置(项目初始化必备)

系统默认缓存空间极小,极易导致缓存失效、被自动清理,项目必须手动配置缓存容量:


// AppDelegate 初始化配置全局缓存
- (void)configNetworkCache {
    // 内存缓存4M,磁盘缓存20M,可根据项目调整
    NSURLCache *cache = [[NSURLCache alloc] initWithMemoryCapacity:4*1024*1024 diskCapacity:20*1024*1024 diskPath:nil];
    [NSURLCache setSharedURLCache:cache];
}

func configNetworkCache() {
    let cache = URLCache(memoryCapacity: 4*1024*1024, diskCapacity: 20*1024*1024, diskPath: nil)
    URLCache.shared = cache
}

4. 单接口动态修改缓存策略(精准控权)

不同接口缓存需求不同,无需全局统一,可单独指定策略:


NSURL *url = [NSURL URLWithString:@"https://xxx.com/home/banner"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 优先读取缓存,优化页面秒开
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;

5. 系统缓存核心踩坑点(项目高频问题)

  • POST 请求默认不缓存:NSURLCache 只默认缓存 GET 请求,POST 无论是否配置协议头,系统均不自动缓存
  • 缓存容量不足自动清理:不手动扩容,默认缓存极小,频繁丢失缓存数据
  • 无法精准控制过期时间:系统缓存完全服从服务端Header,客户端无法单独干预
  • 无针对性清理:只能全局清空缓存,无法单接口、单业务清理

结论:系统缓存适合静态、稳定、低更新频率数据,动态业务数据、个性化数据必须用自定义本地缓存。

五、第三层:业务自定义本地缓存(复杂场景终极方案)

针对系统缓存的短板,项目中必须封装自定义本地缓存,支持:精准过期时间、单接口清理、离线缓存、个性化数据存储、脏数据过滤,完全脱离系统限制。

1. 自定义缓存核心优势

  • 支持 POST/GET 所有请求缓存,不限制请求方式
  • 客户端自定义过期时间,无需依赖服务端配置
  • 支持单条缓存删除、批量清空、过期自动清理
  • 适配用户个性化数据、登录态数据、动态业务接口

2. 极简缓存工具类实战(可直接复用)

基于沙盒+字典归档实现轻量缓存,满足90%业务需求,大型项目可替换为 YYCache。


// 缓存工具核心方法
@interface NetworkCacheManager : NSObject
// 写入缓存(带过期时间)
+ (void)saveCacheData:(id)data key:(NSString *)key expireTime:(NSTimeInterval)time;
// 读取缓存
+ (id)getCacheDataWithKey:(NSString *)key;
// 删除单条缓存
+ (void)removeCacheWithKey:(NSString *)key;
// 清空所有缓存
+ (void)clearAllCache;
@end

核心逻辑:存储数据+时间戳,读取时判断是否过期,过期直接返回空并清理脏数据。

3. 实战场景适配

  • 个人中心数据:缓存时长5分钟,兼顾实时性与加载速度
  • 首页推荐数据:缓存时长10分钟,离线可查看,弱网秒开
  • 本地配置、文案、图标:缓存时长24小时,减少重复请求
  • 临时弹窗、活动数据:自定义短期缓存,活动结束自动失效

六、高阶实战:无感刷新架构(CacheThenNetwork 业界最优方案)

很多项目的痛点:要么纯读缓存不更新,要么纯实时加载白屏卡顿,无法兼顾「秒开体验」和「数据实时性」。

无感刷新(CacheThenNetwork) 是一线大厂通用优化方案,核心逻辑:先加载本地缓存渲染UI(无白屏、秒开),同时后台请求最新数据,数据更新后静默刷新页面,用户无感知

1. 无感刷新完整流程

  1. 页面初始化,优先读取本地缓存,立刻渲染页面,实现秒开
  2. 子线程发起网络请求,拉取服务端最新数据
  3. 对比新老数据,一致则不刷新,不一致静默更新UI并写入新缓存
  4. 全程无loading、无白屏、无闪烁,用户完全无感

2. 无感刷新实战代码(AFN+自定义缓存)


- (void)loadHomeData {
    NSString *cacheKey = @"home_page_data";
    // 1. 优先读取缓存,秒开页面
    id cacheData = [NetworkCacheManager getCacheDataWithKey:cacheKey];
    if (cacheData) {
        [self renderUIWithData:cacheData];
    }
    
    // 2. 后台静默请求最新数据
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    [manager GET:@"https://xxx.com/home/data" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        // 3. 对比数据,不一致则更新UI+缓存
        if (![responseObject isEqualToDictionary:cacheData]) {
            [self renderUIWithData:responseObject];
            [NetworkCacheManager saveCacheData:responseObject key:cacheKey expireTime:600];
        }
    } failure:nil];
}

3. 无感刷新适配场景与禁忌场景

✅ 适合无感刷新

  • 首页、推荐页、资讯列表、静态文案
  • 弱网、离线需要可用的页面
  • 数据更新不频繁、对实时性要求不极致的页面

❌ 禁止无感刷新

  • 支付、订单、钱包、隐私敏感数据
  • 实时榜单、实时聊天、实时行情数据
  • 需要强一致性的业务数据

七、项目落地:三层缓存组合最佳实践(规范可直接上线)

1. 静态资源/基础配置(HTTP强缓存+系统缓存)

图片、图标、静态文案、全局配置,服务端配置 Cache-Control 长期缓存,客户端默认走系统缓存,无需手动干预,极致提速。

2. 动态常规业务(协商缓存+自定义缓存)

首页、列表页,开启 HTTP 协商缓存,同时客户端增加短时自定义缓存,实现 304 复用+离线可用双重保障。

3. 高实时性业务(禁用缓存)

订单、支付、个人中心,设置 ReloadIgnoringLocalCacheData 强制实时请求,杜绝脏数据。

4. 离线可用页面(纯自定义缓存)

历史记录、本地内容页,使用自定义缓存永久存储,仅手动触发更新。

八、高频踩坑复盘(解决项目99%缓存BUG)

坑点1:POST 请求缓存不生效

原因:NSURLCache 系统缓存仅支持GET请求,POST 默认不缓存,协议头配置无效。

解决:POST 接口全部使用自定义本地缓存,手动读写存储。

坑点2:数据更新后,App 一直展示旧缓存

原因:强缓存未过期,客户端不发起校验请求;自定义缓存过期时间过长。

解决:动态调整 Cache-Control 时长,关键接口降低缓存有效期,客户端增加手动刷新、缓存清除逻辑。

坑点3:缓存堆积,App 体积越来越大

原因:系统缓存、自定义缓存无自动清理机制,过期数据持续堆积。

解决:初始化配置合理缓存容量,自定义缓存增加过期自动清理、版本更新清空旧缓存逻辑。

坑点4:304 请求依然返回完整数据

原因:服务端未正确配置 ETag/Last-Modified,客户端请求头未携带校验参数。

解决:前后端对齐协商缓存配置,确保校验头正常传递。

坑点5:无感刷新导致UI闪烁、数据错乱

原因:未做新老数据对比,每次请求都强制刷新UI。

解决:新增数据比对逻辑,仅数据变更时刷新页面。

九、面试高频必背问答

1. iOS 系统为什么 POST 请求不缓存?如何解决?

NSURLCache 系统机制默认只缓存 GET 接口,POST 视为动态请求不做缓存;解决方式是客户端封装自定义本地缓存,手动实现 POST 数据缓存逻辑。

2. 强缓存和协商缓存的区别?

强缓存:有效期内无需请求服务器,直接读本地缓存,速度最快;协商缓存:缓存过期后发起轻量校验,304复用缓存,200更新数据,兼顾速度与实时性。

3. NSURLCache 缓存机制是什么?

系统自动管理的内存+磁盘双缓存,默认遵循HTTP协议缓存策略,仅支持GET请求,可全局配置缓存容量,无法精准控制单接口过期时间与清理规则。

4. 什么是无感刷新?核心优势是什么?

CacheThenNetwork 策略,先读缓存渲染页面保证秒开体验,后台静默请求最新数据,变更后无感更新UI,解决了「白屏卡顿」和「数据滞后」的矛盾,是移动端最优加载方案。

5. 三层缓存如何选型?

静态资源用HTTP+系统缓存、常规动态页面用协商缓存+自定义缓存、高实时页面禁用缓存、离线页面纯自定义缓存。

十、全文总结

  1. HTTP协议缓存:服务端主导,强缓存提速、协商缓存保更新,是基础缓存标准,无需客户端复杂编码。

  2. NSURLSession系统缓存:NSURLCache 自动接管,双缓存机制,适配静态GET接口,存在POST不缓存、控权弱的短板。

  3. 自定义本地缓存:客户端完全可控,适配所有请求、精准控时、按需清理,解决系统缓存所有短板。

  4. 无感刷新架构:CacheThenNetwork 策略,平衡体验与实时性,是中大型项目页面加载的最优解决方案。

  5. 工程核心:三层缓存分层搭配、按需选型、规范落地,可彻底解决iOS项目所有缓存乱象,实现极速加载、离线可用、数据实时的优质体验。