思想以及缓存的那些事

最近的一个项目比较简单,主要以观看视频为主,夹杂一些附加功能,如收藏,分享微博,搜索等。实现起来难度也不高,切换的view来来去去也就那么几个。当然,为了提升用户体验,图片的缓存肯定是必不可少的。之前,我的项目缓存是自己用一些很愚蠢的方法写的,在这里也用不上。自己写的缓存是利用SQLite记录时间,再通过获取Json来判断,缓存的文件是否用得上。而且,最奇葩的是,这个不是Url缓存,因为所使用的token是需要refresh的。因此,ASIHttpRequest那些帮不了我。但这一次情况不一样了,我需要缓存的图片都是有固定Url,也用不着自己实现缓存方法,相当便捷(ASIHttpRequest果然是好东西)。这次快速的项目也使我体会到一些思想为我带来的好处。

一、代码功能的划分

最近看书的时候,有个观点我还是比较认同的,就是一个方法只做一件事。我完成这个项目的时候还没有看到这个观点,但我事后感觉到,这带来的好处不是一般的大。当然,以我现在的水平而言,还是和作者做到的天差地别,但我会努力往这个方向努力。以此次项目为例,我对整个UI的划分还是比较细(其实有一些倒是不必要的,不过勉强可以接受),看起来文件很多,但实际上每个文件的代码并不多。光是首页的Controller就已经超过10个文件(包含头文件)。我不是说分得越多越好,但首页的界面是众多界面中最为复杂的一个,元素比较多,首项的图片还需要自动滚动。我个人认为这种划分还算是比较合理的(好吧,绝对还有很大的改进空间,但和以前比确实有进步了),而其余大部分的就两个文件搞定了,主要因为有一个会被多次用到的TableView被我抽象出来了。这个TableView主要是用作显示视频列表,其实就一行两个视频截图加上两个标题,这个并不复杂。不过,要知道视频列表的数目肯定是用千来计算的,那么一次加载完也不科学,因此必然是要分段加载。于是,我就写了一个协议,在列表滑到最后一项的时候,就调用一下协议的方法,清晰明了(不小心自大了……)。而且由于两个视频一行,因此单数情况也需要自行处理,还有各种各样的杂项。像这个TableView不抽象出来,一旦要改成3个一行,甚至滑动到中间都需要调用协议的方法等,那么工程就大了,一个改动可能就要3、4处地方都要改动。要命一点,甚至这个TableView推倒重来,那打击绝对是毁灭性的。由于获取截图的API是统一的,所以截图的显示也就没有再划分出来了,不过响应对应视频的点击倒是由协议来完成,所以后来点击删除收藏的功能就只是加上几行代码就完成了。

 

二、异步的图片更新

由于我直接使用ASIHttpRequest,加载图片时又不能让UI卡顿,而且加载完以后要马上显示出来。虽然说,ASIHttpRequest有协议的方法在下载完成后回调,但是问题有几个:

1、我下载图片是直接写在NSDictionary的类别中,因此如果想要做到下载完成后立即更新图片的话,就需要把UIImageView的地址传过去,保存在NSDictionary中。问题是,如果同时有第二个View的dictionary是通过第一个传过去的话,那么也不应该重新多开一个任务来重复下载图片。但是又不能直接传同一个UIImageView,因为UIImageView的frame大小不一样。那么对于两个不同frame的UIImageView,都要刷新其Image,如果两个UIImageView都加入到NSDictionary,又要考虑内存释放问题。

2、这时你可能说,自己再写一个类就好了,不过真的有这个必要么?如果你知道,最多更新两个UIImageView还比较好办,但是现在谁知道要同时更新多少个。

3、使用GCD,将同步下载的扔进去子线程,如果失败了,则不断重复子线程的同步下载。当然,如果第二个View打开时还正在下载的话,肯定要做一个标记,免得重复下载。不过问题是,第二个View应该肿么办呢,即使不重复下载,也要在下载完成后立即更新图片啊,这个如何通知第二个View发生了这件事情呢?我就只想到了一种方法,就是在子线程中不断循环判断。网络环境不错的时候,或许问题不大。如果网络比较慢,同时一个TableView同时显示50个小图片呢?50个循环喔,这资源浪费得颇大吧。

我想了比较久这个问题,后来还是用了一个比较猥琐的方法,就是NSTimer。例如像TableView这类东西,可以调用方法遍历现时正在显示的cell。那么就先新建一个timer,设置0.1秒左右的间隔,用循环的方法遍历现时显示的cell的图片。不断调用NSDictionary类别的方法(异步的ASIHttpRequest下载,下载好会直接加载到内存),直接赋值,那么只会有个循环不断执行,而且0.1秒的时间差还不明显,可以理解为下载完立即显示。好处比较明显,就是写起来简单,资源耗费也不算大,但缺点是用户在触摸未放开时,即使下载完图片也是不会更新的。同时滚动动画未结束的话,也是不会更新图片。当然,还有进阶的优化策略,就是设置好scrollView的delegate,当前所有图片都下载好的时候就释放掉timer。当再次滚动的时候就重新启动timer,那么就更能减少timer的循环赋值的资源浪费。

三、缓存文件位置

这个App主要的缓存就是图片,我比较懒,就直接采用了ASIHttpRequest的默认缓存,当然缓存策略还是改为了每次询问服务器请求文件头部、永久储存和请求失败返回缓存。说到这里,不得不说我一直耿耿于怀的事,就是我16G的iPhone现在总是感觉不肿么够用。当储存空间不足的时候,我就会想方设法去多腾出点空间。其中一种重要的手段就是手动清理文件的缓存,不过问题是像QQ这种软件,居然没有手动清理缓存的功能。由于我已经jailbreak,所以iFile用得相当顺手,不过就算是我看着QQ内部这么多文件夹和不知扩展名的文件,我也不敢轻易下手,只好作罢。心想Apple也太不人性化了,居然没有像Android那样的清除文档数据的功能(其实Android的软件卸载也是相当的不干净,跟Windows一个样,可以参考QQ)。

直到这次师弟提交审核后被退回来以后,我才知道,缓存其实不需要自己清理的。Apple规定缓存必须放在指定的位置,如tmp和Library/Cache,那么当用户的储存空间不足的时候,系统就会自动删除这些缓存来获取更多的自由空间。这就是为什么我的Jetpack有好几次丢失服饰设置。用户根本不需要知道这些细节,虽然有些缓存的丢失,我之前真的感觉到有些莫名其妙。当然这个规定也是在iCloud推出后才有的,因为缓存那类文件是不允许上传到iCloud服务器上的(其实Apple也是为了节约成本),用户也省了一些同步的时间和硬盘空间,因为不仅iCloud,iTunes也不会备份这些文件。最后,如果你不希望你程序产生的数据(即非用户产生),被意外删除,同时Apple审核又认定你这些数据不能用来备份和同步的话,你可以为这些文件加上一个标记,方法如下:

 1 #include <sys/xattr.h>
 2 - (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
 3 {
 4     const char* filePath = [[URL path] fileSystemRepresentation];
 5 
 6     const char* attrName = "com.apple.MobileBackup";
 7     u_int8_t attrValue = 1;
 8 
 9     int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
10     return result == 0;
11 }

虽然这个细节我最近才知道,但不禁感叹,Apple居然为用户干了这么多的事情,表面很简单,但内里却这么复杂,不过用户不需要知道。PS:Time Machine也是默认忽略备份缓存的,如果开发者遵守后这些规范的话,备份确实是会变得更为高效,无疑是对用户最有利的。

原文地址:https://www.cnblogs.com/ipinka/p/2870057.html