IOS Cache设计

Cache的设计是个基础计算机理论,也是程序员的重要基本功之一。Cache几乎无处不在,CPU的L1 L2 Cache,iOS系统的clean page和dirty page机制,HTTP的tag机制等,这些背后都是Cache设计思想的应用。
为什么需要Cache
Cache的目的是为了追求更高的速度体验,Cache的源头是两种数据读取方式在成本和性能上的差异。
在开始着手设计Cache之前,需要先理清数据存储的媒介。作为客户端开发人员来说,我们所关注的数据存储方式也有不少种:
上面所说的每一个环节,都存在性能和成本上的差别,Server的数据自然是最及时最准确的,但一个App要以NSArray的形式获取到Server的数据,中间要经过「漫长」的过程,可以说每一步中都存在cache的设计思想。
对于Cache的理解和实践,前提是我们对于存储媒介,和不同数据结构差异,有比较深入的掌握。
我们大部分App的性能优化,如果涉及到Cache,一般都是在Memory这一媒介上做处理。将需要从Disk中,或者通过CPU复杂计算才能获取的数据,通过合理的数据结构存储在Memory中,就能解决我们App开发里,绝大部分的Cache需求了。这一层面的Cache设计也有着不同的姿势,先来看看简单可用型。
简单可用型Cache
得益于Foundation中NSDictionary的封装,我们可以用hash表这种数据结构来实现一个简单可用的cache机制,先来看一个实例:
- (NSString*)getFormmatedPhoneNumber:(NSNumber*)phone
{
if(phone == nil)
{
return nil;
}
return [PhoneFormatLib formatPhoneNumber:phone]; //CPU费时操作
}
这是个简单的格式化手机号码的函数,其中 formatPhoneNumber 函数是个CPU Intensive的调用,而且在业务场景中针对同一个手机号码,需要经常性的获取格式化之后的NSString,如果每次都重复计算显然是对CPU资源的浪费,而且性能也不好。我们可以加个简单的Cache来优化:
static NSMutableDictionary* gPhoneCache = nil;
- (NSString*)getFormmatedPhoneNumber:(NSNumber*)phone
{
if(phone == nil)
{
return nil;
}
NSString* phoneNumberStr = nil;
[_phoneLock lock];
if(gPhoneCache == nil)
{
gPhoneCache = @{}.mutableCopy;
}
phoneNumberStr = [gPhoneCache objectForKey:phone];
if (phoneNumberStr == nil) {
phoneNumberStr = [PhoneFormatLib formatPhoneNumber:phone];
[gPhoneCache setObject:phoneNumberStr forKey:phone];
}
[_phoneLock unlock];
return phoneNumberStr;
}
通过引入NSMutableDictionary,就避免了每次都需要重复调用 formatPhoneNumber 的问题,so easy就完成了一个快速的cache设计,马上就可以提交给测试,把优化成果甩产品经理脸上,这归功于hash表O(1)的时间复杂度。内存空间会多消耗一些,不过对于小量的数据影响比较小,现代的hash表不会一开始就分配大量的空间,而是随着数据的增加而逐渐扩容。
这种简单可用型的Cache设计,最大的问题在于,代码过于零散且不可控。小量且分散的cache设计几乎等同于挖坑,在你设计cache的时候可能数据量还小,但后面维护的时候,业务改变的时候,谁也不能保证这块内存的开销依然可以忽略不计。而且这种内存方面的损耗很难察觉,巧妙的隐蔽在某个.m文件中,到后期想控制整个App的内存开销时,会感觉到处都有坑,无从下手。你可能也发现了,上面这段Cache代码没有释放Cache的地方。
所有对我们整个App有副作用的代码都需要被集中管理,要能从架构的层面去理解和定位。怎么去定义副作用呢?可以抽象成一种「写操作」,往Cache中添加新的记录就是写操作,这种写操作的副作用是额外的内存开销,Cache的本质是以空间换时间,这空间损耗就是我们的副作用,一个副作用会引发其他更多的副作用,理清这些副作用往往需要反复查阅大量的代码。更好的办法是,一开始就把有副作用的代码集中管理。
优雅可控型Cache
避免Cache代码散乱放置的做法是,设计一个优雅可控的Cache模块。一个App中,可能会有各种各样的数据需要Cache,phoneNumberCache,avatarCache,spaceshipCache等等,我们需要有个源头来追踪这些cache,直观的做法是通过工厂类来生成和持有这些各式各样的cache:
//CacheFactory.h @interface CacheFactory : NSObject + (instancetype)sharedInstance; - (id<MyCacheProtocol>)getPhoneNumberCache; - (void)clearPhoneNumberCache; - (id<MyCacheProtocol>)getAvatarCache; - (void)clearAvatarCache; @end
这样当我们需要评估各种Cache对整个App内存开销的影响之时,只需要从CacheFactory代码着手即可,调试起来也有迹可循,其他工程师接手你的代码也会感激涕零的。
通过protocol的方式,将cache的声明和实现想分离,这也是个好习惯。cache的另一个重要知识点是cache的淘汰策略,不同的策略表现也不一样,FIFO,LRU,2Queues等等,现在有不少成熟的第三方cache框架可以使用,系统也提供了淘汰策略不明确的NSCache,如果没有动手写过任何cache淘汰策略,我还是建议大家自己动手试着做一个,至少要读一下相关的实现源码,了解这些淘汰策略很有必要,在做一些深度优化的时候需要因地制宜来做决定。
cache的使用要有收有放,不能只创建不释放,事实上,所有涉及到data的操作都要考虑data的生命周期。我们做业务的时候,多是以Controller为基础单位,有些场景下,一个Controller在退出之后被再次进入的可能性就非常之低了,适时的清理cache会让我们App的整体表现更好。
Immutable Cache
Cache中存放的是啥?是Data。说到Data,就不得不提peak君最爱啰嗦的”Immutability(不可变性)”了,Immutability和我们代码的稳定性有着极大的关系,大到就像「房间里的大象」,很重要也容易被忽视。
在实践Immutability的时候,需要先将Data做分类,再去区分每一种类型Data如何去实施不可变性。做Data分类最重要的是分清楚值类型和引用类型的差别。传值的时候传递的是新的内存拷贝,所以值类型大多是安全的,传指针的时候传递的是同一块共享内存空间,这也是指针之所以危险的一大原因。bool,Int,long等等这些primitive type都是值类型,可以放心的传递,而对象类型往往是以指针的形式在传递,需要特别的注意,我们一般通过copy的方式(生成新的内存拷贝)来传递。这也是为什么Swift中将很多原先在Objective C中基础类变为值类型的原因,强化Immutability,让我们的代码更加安全。
我们看下不同类型的数据在Cache中的读写操作。
值类型-读
值类型可以安心返回:
- (int)spaceshipCount
{
//...
return _shipCount;
}
值类型-写
值类型也可以安全的写:
- (void)setSpaceshipCount:(int)count
{
_shipCount = count;
}
对象类型-读
指针类型需要生成新拷贝:
- (User*)luckyUser
{
//...
return [_luckyUser copy];
}
对象类的copy方法需要我们手动实现NSCopying protocol,开发的初期虽然显得繁琐了些,但后期的回报很大。而且这里的copy必须是deep copy,User中的每一个被持有的property都需要递归copy。
对象类型-写
对象类型写操作的危险之处在于函数的入参,入参也是对象类型的话,传入的是一个共享的引用:
- (void)setLuckyUser:(User*)user
{
//...
_luckyUser = [user copy];
}
集合类型-读
集合类也需要copy,是bug和crash的重灾区:
- (NSArray*)hotDishes
{
//...
return [_hotDishes copy];
}
集合类型-写
- (void)setHotDishes:(NSArray*)dishes
{
//...
_hotDishes = [dished copy];
}
看到这里,大家可能也发现了,其实原则也比较简单,只要保证业务模块从Cache中获取的数据都是独立的copy,就能避免数据共享带来的各种隐患。Cache模块有点类似函数式编程中的纯函数,既不依赖于外部的状态,也不会修改外部的状态,重点处理每一个函数调用的input(入参)和output(返回值)即可。
多线程安全
Cache多线程安全的重点在于对集合类的处理,Cache本身多数时候都是在管理数据的集合。需要特别注意的是NSString其实也应该归到集合类,从数据读写和多线程安全方面看,NSString和NSArray在很多方面表现都是一致的。一些成熟的第三方Cache库已经替我们处理好了多线程安全的问题,如果是自己造的轮子,尤其要注意保证读写都是原子操作,至于如何使用锁,相关的文章分享已经很多了,此处不做赘述了。
总结
了解Cache关键在于明白其背后的设计思想,进而能对我们App的行为有更全面的掌握,能明白每一个业务流程背后对数据处理的瓶颈在哪。随着代码越写越多,业务越来越复杂,今天或明天,我们总要遇到需要应用Cache设计的时候。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
# IOS
# Cache设计
# Cache详解及实例
# Cache
# iOS中的NSURLCache数据缓存类用法解析
# Objective-C的缓存框架EGOCache在iOS App开发中的使用
# C++开发在IOS环境下运行的LRUCache缓存功能
# 使用Javascript判断浏览器终端设备(PC、IOS(iphone)、Android)
# iOS 条码及二维码扫描(从相册中读取条形码/二维码)及扫码过程中遇到的坑
# iOS实现时间显示几分钟前
# 几小时前以及刚刚的方法示例
# IOS正则表达式判断输入类型(整理)
# iOS 仿百度外卖-首页重力感应的实例
# IOS 开发之应用唤起实现原理详解
# IOS TextFiled与TextView 键盘的收起以及处理键盘遮挡
# IOS与网页JS交互详解及实例
# 的是
# 都是
# 数据结构
# 这一
# 是个
# 也会
# 多线程
# 每次都
# 是在
# 也有
# 就能
# 设计思想
# 涉及到
# 第三方
# 递归
# 要快
# 后期
# 数据存储
# 发现了
# 这也是
相关文章:
如何在阿里云购买域名并搭建网站?
如何快速搭建高效服务器建站系统?
高端网站建设与定制开发一站式解决方案 中企动力
小说建站VPS选用指南:性能对比、配置优化与建站方案解析
广州营销型建站服务商推荐:技术优势与SEO优化解析
如何处理“XML格式不正确”错误 常见XML well-formed问题解决方法
大学网站设计制作软件有哪些,如何将网站制作成自己app?
如何快速启动建站代理加盟业务?
建站之星导航配置指南:自助建站与SEO优化全解析
建站之星如何实现网站加密操作?
建站之星×万网:智能建站系统+自助建站平台一键生成
免费公司网站制作软件,如何申请免费主页空间做自己的网站?
建站之星体验版:智能建站系统+响应式设计,多端适配快速建站
宝塔建站无法访问?如何排查配置与端口问题?
想学网站制作怎么学,建立一个网站要花费多少?
网站制作费用多少钱,一个网站的运营,需要哪些费用?
头像制作网站在线观看,除了站酷,还有哪些比较好的设计网站?
网站制作大概要多少钱一个,做一个平台网站大概多少钱?
正规网站制作公司有哪些,目前国内哪家网页网站制作设计公司比较专业靠谱?口碑好?
Python如何创建带属性的XML节点
如何选择高效稳定的ISP建站解决方案?
建站之星如何助力网站排名飙升?揭秘高效技巧
如何通过西部建站助手安装IIS服务器?
nginx修改上传文件大小限制的方法
西安专业网站制作公司有哪些,陕西省建行官方网站?
Android滚轮选择时间控件使用详解
如何用IIS7快速搭建并优化网站站点?
黑客如何利用漏洞与弱口令入侵网站服务器?
如何通过智能用户系统一键生成高效建站方案?
如何在Windows环境下新建FTP站点并设置权限?
如何快速重置建站主机并恢复默认配置?
如何高效利用亚马逊云主机搭建企业网站?
制作无缝贴图网站有哪些,3dmax无缝贴图怎么调?
网站制作公司哪里好做,成都网站制作公司哪家做得比较好,更正规?
如何制作新型网站程序文件,新型止水鱼鳞网要拆除吗?
测试制作网站有哪些,测试性取向的权威测试或者网站?
如何在阿里云部署织梦网站?
如何用搬瓦工VPS快速搭建个人网站?
浅谈Javascript中的Label语句
南阳网站制作公司推荐,小学电子版试卷去哪里找资源好?
完全自定义免费建站平台:主题模板在线生成一站式服务
香港服务器部署网站为何提示未备案?
网站设计制作企业有哪些,抖音官网主页怎么设置?
如何做网站制作流程,*游戏网站怎么搭建?
电商网站制作公司有哪些,1688网是什么意思?
如何高效完成自助建站业务培训?
建站之星如何取消后台验证码生成?
实惠建站价格推荐:2025年高性价比自助建站套餐解析
如何快速搭建高效香港服务器网站?
如何生成腾讯云建站专用兑换码?
*请认真填写需求信息,我们会在24小时内与您取得联系。