全文约 2500 字,预计阅读时间约 5 分钟。
前言
事情的起因是这样子的,前段时间闲来无聊,去翻看手机的储存空间统计,发现微信、QQ、微博等这些 app 的占用空间都很大,点开查看详情,文稿与数据占了很大一部分。虽然这些 app 自带清理缓存功能,但是清理完了之后缓存还是很大。微信和 QQ 我就不说了,有很多聊天记录或者文件没有删除。但是像微博、淘宝、京东等 app ,在使用了自带的清除缓存功能之后,其在 iOS 系统显示的文稿与数据占用还是很高,让我很不能理解。由于之前使用的 16G 储存空间的 iPhone,被储存空间不足折磨不停,因此希望 app 都能做好储存空间的管理。否则很多用户只能用脚投票了。作为一个小透明,我只是呼吁各位开发者能够做好这个,而我在学习的过程中也会尽量注意这个情况。
鄙校论坛的官方 iOS 客户端也提供清理缓存的功能,我就去看了看。发现其文稿与数据占用量极高(200 Mb+),而app自身提供的清理缓存所统计的数据量又极低(不到 10 Mb)。正好我自己也在做一款app练手,也正好做到缓存这一块,因此就想探讨一下文稿与数据包含了什么,应该怎么清理缓存。
北邮人论坛 iOS 客户端开发者:Caches里有个fsCachedData之前没发现。。需要NSURLCache.sharedURLCache().removeAllResponses()搞掉。。
至于为什么不写如何进行缓存,一是我自己还没怎么懂,二是网上已经有很多人整理得挺好的了,我就不献丑了。
沙盒(SandBox)系统
iOS文件系统将不同的app隔离,让他们自己运行。为了保证系统简洁,iOS设备的用户不能直接访问文件系统,每一个app都保存在一个沙盒中,一般来说,该app只能访问沙盒中的数据。iOS通过沙盒系统,保护系统与其他app的安全。
iOS 标准目录:文件存在哪儿
为了安全起见,iOS app 与文件系统的交互被限制在app的沙盒目录下。在安装一个新 app 的过程中,安装器给在沙盒中给 app 创建了一系列的容器。每一个容器有其特殊的职责。bundle 容器装着 app 的 bundle,数据容器则包含着 app 和用户的数据。数据容器分成了几个子文件夹,app 可以使用这些文件夹来对数据进行排序与管理。
如何查看 app 的沙盒目录(模拟器 + 真机)
网上搜到的方法很多是几年前的方法,就算是今年发表的文章上面的图也是直接抄的前几年的图片,因此没有参考价值。本文将与时俱进,采用 Xcode 8.1 + iOS 10.1 的环境介绍如何查看app的沙盒文件。
查看 iOS 模拟器上 app 的沙盒目录
查看在 iOS 模拟器上 app 的沙盒目录,只需要一段代码就可以解决问题。
是的,你没看错,在模拟器上面一段代码就能解决困扰我两天的问题。在前人的指导下走了很多弯路,最后发现了NSHomeDirectory()
这个方法。
NSLog(@"%@",NSHomeDirectory());
输出结果
/Users/username/Library/Developer/CoreSimulator/Devices/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/data/Containers/Data/Application/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
将输出结果打开 Finder->前往->前往文件夹,粘贴。即可查看 app 的沙盒目录。
查看 iOS 真机上 app 的沙盒目录
查看 iOS 真机上 app 的沙盒目录比在模拟器上稍显复杂,但也可以轻松实现。
1.在 Xcode 的上部导航栏中选择 Window,随后选择 Devices。
2.选择相应的 Devices 后,在 Installed Apps 里面选择要打开查看的app,点击下边的齿轮,可以用 Download Container 将沙盒文件保存到本地。
3.在本地中将沙盒文件点击右键,选择显示包内容,即可查看沙盒文件。
注意:在前面提到的环境中,上述方法只能查看 app 的 Data Container, Bundle Container 不在这一目录下。
在沙盒目录中,我们可以看到 Documents/
、Library/
、tmp/
三个文件夹。而根据 Apple 提供的文件系统编程指南官方文档,我们可以对上述文件夹中应当存放何种文件进行了解。以下内容翻译自 文件系统编程指南。
表1 一个 iOS app 通常使用的目录
目录名 | 描述 |
---|---|
appName.app | 这个是 app 的 bundle 。这个目录包含了 app 和其所有的资源。 你没有这个目录的写权限。为了防止篡改,bundle 目录的签名在 bundle 生成时生成。修改这个目录会修改签名并阻止 app 登录。你只可以对所有保存在 bundle 里的资源有读权限。更多内容,参考资源编程指南(Resource Programming Guide)。 这个目录下的内容不会保存在 iTunes 或者 iCloud。iTunes 会对从 app Store 购买的任何应用执行初始同步。 |
Documents/ | 使用这个目录来保存用户生成的文件。用户可以通过文件分享功能访问这个目录的内容。因此,这个目录你应该只放一些你希望展示给用户的文件。 这个目录的文件会被 iTunes 和 iCloud 备份。 |
Documents/Inbox/ | 使用这个目录访问你的app打开的外部实体。特别地,邮件客户端会将和你的app相关的附件放在你的app的这个目录。文件交互控制器也可能会在这个文件夹存放东西。 在这个文件夹里,你的app可以读或者删除文件,但是不能创建新文件或者修改现有的文件。如果用户想要在这个文件夹里尝试去修改文件,你的app需要悄悄地把该文件移除这个文件夹。 这个目录的文件会被 iTunes 和 iCloud 备份。 |
Library/ | 这是一个所有非用户数据文件的顶级目录。你通常把文件放到多个标准子目录下中的一个。iOS app 通常使用 application Support 和 Cache 子文件夹,你也可以创建自己的子文件夹。使用 Library 子文件夹来存放任何你不想被用户看到的文件。你的app不应该使用这些文件夹来存放用户数据文件。 除了 Caches 子文件夹,Library 文件夹下的内容会被 iTunes 和 iCloud 备份。 |
tmp/ | 使用这个文件夹来存放在临时文件,这些临时文件在两次打开 app 之间不一定需要保存。当不在需要这些文件时,你的 app 需要这个文件夹中移除它们。系统也可能会在你的 app 不再运行的时候清除这个文件夹。 这个目录的文件 不会 被 iTunes 和 iCloud 备份。 |
一个 iOS app 可以在 Documents
、Library
和 tmp
文件夹下建立新的文件夹。在这个目录下你需要更好地安排文件放置的位置。
app 的文件存放的地方
为了防止 iOS 设备同步与备份的过程持续太久,注意选择存放文件的位置。存放了大量文件的 app 会减慢 iTunes 或者 iCloud 的备份速度。这些 app 也会消耗大量的用户可用储存空间,这会导致用户删除 app 或者不允许 app 的数据备份到 iCloud 中。因此,保存 app 的数据需要遵循以下几点准则。
- 将用户数据保存在
Documents/
. 用户数据包括所有你希望展示给用户的文件——那些你希望用户创建、引入、删除或编辑的。对于一个绘画 app,用户数据包括任何用户想要创建的图片分拣。对于一个文本编辑器,用户数据包括文本文件。音视频 app 的用户数据则可能包括用户已经下载的想要随后观看或收听的文件。 - 将 app 创建的支持文件保存在
Library/Application support/
文件夹。通常地,在这个文件夹下包括那些 app 运行时需要使用但应该对用户隐藏的文件。这个文件夹也包括从 app bundle 读取的数据文件、配置文件、模板和修改版本资源。 - 要记住在
Documents/
下和Application support/
文件夹在默认情况下会被备份。你可以通过调用-[NSURL setResourceValue:forKey:error:]
使用NSURLIsExcludedFromBackupKey
值来防止一些文件被备份。所有可以重复创建或者下载的文件必须排除在备份外。特别包括那些大的多媒体文件。如果你的应用下载音视频文件,保证它们不在备份中。 - 将临时数据保存在
tmp/
文件夹中。临时文件包括那些在很长一段时间内不需要持续存在的文件。记得在使用这些文件结束后删除它们防止它们继续占用用户设备的空间。系统会在你的 app 非运行的时候周期性地清除这些文件,因此你不能保证这些文件在你的 app 终止后还继续存在。 - 将数据缓存文件保存在
Library/Caches/
文件夹中。缓存文件包括那些比临时文件保存时间要长,但是没有支持文件时间这么长的文件。通常来说,应用在没有缓存数据时可以正常运行,但是在存在缓存文件时能够提升性能。缓存文件包括(但不限于)数据库缓存文件以及短暂的可下载内容。需要注意的是,系统可能会删除Caches/
文件夹里的内容来释放磁盘空间,因此你的 app 需要保证这些文件是可以重新创建或者下载的。
计算缓存大小与删除缓存
重要的事情说三遍:
数据无价!
数据无价!
数据无价!
在进行数据操作时,请牢记数据无价这一原则。防止因为误操作将用户宝贵的数据删除,造成不必要的麻烦。
计算缓存大小
- (NSUInteger)getSize {
NSUInteger size = 0;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString * cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSDirectoryEnumerator *fileEnumerator = [fileManager enumeratorAtPath:cachePath];
for (NSString *fileName in fileEnumerator) {
NSString *filePath = [cachePath stringByAppendingPathComponent:fileName];
NSDictionary *attrs = [fileManager attributesOfItemAtPath:filePath error:nil];
size += [attrs fileSize];
}
return size;
}
使用 NSCachesDirectory
得到的路径是 Library/Caches/
文件夹,其他文件夹如 Documents/
和 Library/
路径的获取,请参照 NSSearchPathDirectory 。
使用 enumeratorAtPath
获取文件夹下所有文件/文件夹的路径。类似的方法包括 subpathsAtPath
和 subpathsOfDirectoryAtPath
。
删除缓存
再提醒一遍,删除之前请再三确认删除的文件不会影响程序的正常运行。当然了,如果保存 app 的数据没有按照上述提到的准则,则在审核时可能无法通过。
/*
12-23 update: use removeItemAtPath to delete each subdir
instead of delete the root dir and then recreate it
*/
- (void)clearFile
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString * cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSLog(@"%@",cachePath);
NSDirectoryEnumerator *fileEnumerator = [fileManager enumeratorAtPath:cachePath];
for (NSString *fileName in fileEnumerator) {
NSString *filePath = [cachePath stringByAppendingPathComponent:fileName];
[fileManager removeItemAtPath:filePath error:nil];
}
}
通过 enumeratorAtPath
使用 removeItemAtPath
将文件夹内的所有子文件与文件夹删除。注意:当使用 removeItemAtPath
将删除文件夹下所有内容包含自身文件夹,与并使用 createDirectoryAtPath
重新创建该文件夹时,第二次调用 removeItemAtPath
不一定能将之前手动创建的文件夹删除,造成缓存堆积。
遇到的一些问题
- 在真机调试的时候,某些数据死活删不去,而在模拟器上没有这个问题。考虑到可能是上一个版本遗留的文件,手动删掉 app 再重新倒入就好了。
- 真机调试的时候,
Library/Caches/Snapshots/
里的内容删不去。至今没有找到解决方案。 - 如何定时清理缓存也是一个研究点。
写在最后
本篇文章通过介绍沙盒系统,查看沙盒文件,通过代码计算缓存大小与删除缓存四个部分对 iOS app 对文稿与数据部分进行理解和分析。想要深入了解,可以阅读 Apple 提供的文件系统编程指南官方文档,和 SDWebImage 的 SDImageCache 部分源码。
参考文献
[1] Apple 文件系统编程指南官方文档
[2] Apple API Reference NSFileManager 部分
[3] iOS查看沙盒文件图文教程(真机+模拟器)-Darren.Von
[4] SDWebImage 源码