很多app都有用户系统。无论是自己实现还是使用第三方,都可能需要展示用户的头像。在比较常见的场景下,头像会出现在一些列表中,比如联系人列表、消息列表等。虽然头像也是图片,但是我们对头像的要求比普通图片要高。头像的原始图片可能有各种尺寸,但在应用中,我们可能需要某种风格的头像,比如正方形或圆形。如果我们使用常见的图片缓存工具如SDWebImage、Kingfisher等,那么我们就需要自己对图片进行裁剪处理。如果直接使用UIImageView进行缩小,画面的细节会变得过于“锐利”,影响观看。此外,一个应用程序中的头像样式可能不止一种。比如有的场景要用大头像,有的场景要用小头像,有的场景要用原尺寸头像;或者有的场景要用方形头像,有的场景要用圆角矩形头像,等等。脚。注意:单纯使用layer.cornerRadius或CALayer遮罩会导致列表的滑动性能出现问题,故不考虑。如果我们要优化的话,我们当然希望这些不同风格的小头像可以存储在本地,这样我们就不需要从网络上获取然后再对风格进行裁剪或者处理。一来减少不必要的流量消耗,二来提高头像的加载速度,用户体验自然会更好。基于以上分析,我们来设计一个头像缓存系统。它的目标是快速获取和缓存样式化的头像图像,并且可以很容易地集成到现有项目中。一些前提条件:头像的图片网址是唯一的,即不同的头像有不同的网址。如果用户更换了头像,新头像的URL与旧头像不同;头像为公共资源,无需验证即可下载。好吧,让我们做一个智力游戏。首先,现有的用户模型可能是:structUser{letuserID:Stringletusername:StringletavatarURLString:String//...}其中avatarURLString表示远程头像链接。如果我们要下载它,最简单的方法是直接使用NSData的构造函数:ifletURL=NSURL(string:avatarURLString),data=NSData(contentsOfURL:URL),image=UIImage(data:data){//TODO}如果某个列表中有5个item是同一个用户生成的,那么这个用户的头像会同时显示5次,难道我们要下载5次吗?为了解决这个问题,我们可以把获取头像的行为看成是一个“请求”:requestpool):varrequests:[Request]这样每次要下载某个头像,就构造一个request。先放到请求池中,然后查看请求池中包含这个avatarURLString的请求数。如果数字大于1,说明之前有过相同的请求,我们不执行下载操作,静静等待**第一次请求帮助。NaviAvatarPod过了一会儿,之前请求的下载就结束了。这时候只需要执行完成所有具有相同avatarURLString的请求,后面的一个(或几个)就会“免费”获得服务。以上是第一次需要下载的情况。如果我们已经下载了头像图片,我们仍然可以构造请求,但是之后不会执行下载操作,而是去文件系统中查找。同样,我们的请求可以包含样式。如果同一个用户的五个头像风格不同,我们只需要在每个请求的完成最终执行时分别处理风格即可。既然下载后需要保存,那怎么保存比较好呢?有些人倾向于直接使用文件系统API来将数据存储在文件系统中;有些人已经用过CoreData,那么他会倾向于把它放在CoreData中,CoreData实体的一些属性类型有Externalcharacteristics。检查后,不占用内存空间,避免了直接操作文件系统的繁琐;有的人用Realm或者直接用SQLite等,反正存储过程都差不多,只是细节不一样。我的建议是将原图保存在文件系统中(或者CoreData选择External),小样式的头像可以直接作为属性保存到数据库中(通常,比如CoreData或者Realm支持NSData属性,但不会太大,因为它们会留在内存中)。不幸的是,如果我们现在要做一个缓存头像的框架,我们不可能以非常具体的方式为用户存储文件。因为细节差别太大,实在顾不上了。而且即使有,用户也很难将这一步集成到现有代码中。如果不考虑用户的喜好,我们实际上做的是一个“通用”的图片缓存系统。这里的一般目的理解为“不够优化”。好在我们可以定义一个协议,在协议中声明存储API,用户自己实现存储过程。我们的缓存系统只需要调用API,不需要关心存储过程的细节。考虑到头像样式,有:,styledImage:UIImage)}稍微解释一下:URL自然表示头像的原始链接;style表示本次要展示的头像的风格,后面会讲到;placeholderImage很容易理解。在展示真实头像之前,需要一个placeholder,不同的App设计不同,所以也交给了用户;localOriginalImage表示本地存储的原始图像。既然存储是用户控制的,那么读取自然也是用户控制的;localStyledImage代表本地风格化身。提供了不同的风格;最好的是saveOriginalImage(originalImage:styledImage:),存储过程由用户控制,包括原始图像和本次样式化图像。其中,头像样式可以做成如下:enumAvatarStyle:Equatable{caseOriginalcaseRectangle(size:CGSize)caseRoundedRectangle(size:CGSize,cornerRadius:CGFloat,borderWidth:CGFloat)typealiasTransform=UIImage->UIImage?caseFree(name:String,transform:Transform)}有原始样式(未处理)、固定大小矩形(可生成正方形等)、透明边圆角矩形(可生成圆形等)、自由样式,由用户提供用于图像变换功能。看起来比较完整,可以补充。然后我们扩展UIImageView一个方法:extensionUIImageView{funcnavi_setAvatar(avatar:Avatar){//TODO}}方便用户使用。***示意图如下:Navi的缓存架构用户有一个列表要展示,列表项中包含头像,所以构造一个头像描述来唤醒AvatarPod中的头像。AvatarPod避免了重复下载并实现了整个逻辑。比如先去内存缓存中查看,如果没有,查看用户是否提供了本地图片,如果没有,则下载。下载后对样式进行处理,将原图和样式图交给用户保存。那么在具体集成方面,用户只需让自己的User实现Avatar协议或者构造一个新的“中间对象”,其中包含User和AvatarStyle,让这个对象实现Avatar协议即可。我推荐使用中间对象的方式,这样对已有的代码改动不大,对多种样式的支持也更好。具体可以参考Navi的代码和Demo。文件不多,应该比较容易看懂。但是,要运行演示,iOS设备需要在“设置”中登录Twitter帐户。没有推特账号的同学可以去twitter.com注册一个。***,Navi的名字来源于电影《阿凡达》中的Na'vi,潘多拉星球上的智慧类人生物。人类到达潘多拉后,为了更好地与原始纳威人交流,利用基因改造合成了一种可以被人类控制的类纳威生物,即阿凡达(意为化身或替身).原文链接:https://github.com/nixzhu/dev-blog
