全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

IOS Cache设计详细介绍及简单示例

IOS Cache设计

Cache的设计是个基础计算机理论,也是程序员的重要基本功之一。Cache几乎无处不在,CPU的L1 L2 Cache,iOS系统的clean page和dirty page机制,HTTP的tag机制等,这些背后都是Cache设计思想的应用。

为什么需要Cache

Cache的目的是为了追求更高的速度体验,Cache的源头是两种数据读取方式在成本和性能上的差异。

在开始着手设计Cache之前,需要先理清数据存储的媒介。作为客户端开发人员来说,我们所关注的数据存储方式也有不少种:

  • 数据最开始是存储在Server上,这些数据需要通过网络请求获取。
  • 从Server获取数据时,会经过各种中间网络节点(比如代理),这些节点有时会缓存我们的数据。
  • 把数据下载到本地之后,我们会在本地disk缓存一份,这样或许不用每次都重新去服务器请求。
  • 存到disk之后,数据的存储方式会影响到读取的速度,以B+ Tree存储的sqlite就比直接序列化NSArray到文件之中要快不少。
  • App启动时,系统会将从Server下载到的数据,从disk加载到memory,memory的读写性能比disk要快很多。
  • 到了Memory中,不同的数据结构存储方式也会存在速度上的差异。用NSDictionary(hash表)形式存储读数据,写性能都比Array好,但space开销更大。虽说memory的读写性能比disk都高了很多,但在大集合类数据操作的时候有时也会遇到瓶颈。
  • 比Memory更快的还有Register,L1,L2,只不过对于iOS App开发来说,很少深入到这一层面的优化。

上面所说的每一个环节,都存在性能和成本上的差别,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小时内与您取得联系。