全文約 2500 字,預(yù)計(jì)閱讀時間約 5 分鐘。
前言
事情的起因是這樣子的,前段時間閑來無聊,去翻看手機(jī)的儲存空間統(tǒng)計(jì),發(fā)現(xiàn)微信、QQ、微博等這些 app 的占用空間都很大,點(diǎn)開查看詳情,文稿與數(shù)據(jù)占了很大一部分。雖然這些 app 自帶清理緩存功能,但是清理完了之后緩存還是很大。微信和 QQ 我就不說了,有很多聊天記錄或者文件沒有刪除。但是像微博、淘寶、京東等 app ,在使用了自帶的清除緩存功能之后,其在 iOS 系統(tǒng)顯示的文稿與數(shù)據(jù)占用還是很高,讓我很不能理解。由于之前使用的 16G 儲存空間的 iPhone,被儲存空間不足折磨不停,因此希望 app 都能做好儲存空間的管理。否則很多用戶只能用腳投票了。作為一個小透明,我只是呼吁各位開發(fā)者能夠做好這個,而我在學(xué)習(xí)的過程中也會盡量注意這個情況。
鄙校論壇的官方 iOS 客戶端也提供清理緩存的功能,我就去看了看。發(fā)現(xiàn)其文稿與數(shù)據(jù)占用量極高(200 Mb+),而app自身提供的清理緩存所統(tǒng)計(jì)的數(shù)據(jù)量又極低(不到 10 Mb)。正好我自己也在做一款app練手,也正好做到緩存這一塊,因此就想探討一下文稿與數(shù)據(jù)包含了什么,應(yīng)該怎么清理緩存。
北郵人論壇 iOS 客戶端開發(fā)者:Caches里有個fsCachedData之前沒發(fā)現(xiàn)。。需要NSURLCache.sharedURLCache().removeAllResponses()搞掉。。
至于為什么不寫如何進(jìn)行緩存,一是我自己還沒怎么懂,二是網(wǎng)上已經(jīng)有很多人整理得挺好的了,我就不獻(xiàn)丑了。
沙盒(SandBox)系統(tǒng)
iOS文件系統(tǒng)將不同的app隔離,讓他們自己運(yùn)行。為了保證系統(tǒng)簡潔,iOS設(shè)備的用戶不能直接訪問文件系統(tǒng),每一個app都保存在一個沙盒中,一般來說,該app只能訪問沙盒中的數(shù)據(jù)。iOS通過沙盒系統(tǒng),保護(hù)系統(tǒng)與其他app的安全。
iOS 標(biāo)準(zhǔn)目錄:文件存在哪兒
為了安全起見,iOS app 與文件系統(tǒng)的交互被限制在app的沙盒目錄下。在安裝一個新 app 的過程中,安裝器給在沙盒中給 app 創(chuàng)建了一系列的容器。每一個容器有其特殊的職責(zé)。bundle 容器裝著 app 的 bundle,數(shù)據(jù)容器則包含著 app 和用戶的數(shù)據(jù)。數(shù)據(jù)容器分成了幾個子文件夾,app 可以使用這些文件夾來對數(shù)據(jù)進(jìn)行排序與管理。

如何查看 app 的沙盒目錄(模擬器 + 真機(jī))
網(wǎng)上搜到的方法很多是幾年前的方法,就算是今年發(fā)表的文章上面的圖也是直接抄的前幾年的圖片,因此沒有參考價值。本文將與時俱進(jìn),采用 Xcode 8.1 + iOS 10.1 的環(huán)境介紹如何查看app的沙盒文件。
查看 iOS 模擬器上 app 的沙盒目錄
查看在 iOS 模擬器上 app 的沙盒目錄,只需要一段代碼就可以解決問題。
是的,你沒看錯,在模擬器上面一段代碼就能解決困擾我兩天的問題。在前人的指導(dǎo)下走了很多彎路,最后發(fā)現(xiàn)了NSHomeDirectory()這個方法。
NSLog(@"%@",NSHomeDirectory());
輸出結(jié)果
/Users/username/Library/Developer/CoreSimulator/Devices/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/data/Containers/Data/Application/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
將輸出結(jié)果打開 Finder->前往->前往文件夾,粘貼。即可查看 app 的沙盒目錄。

查看 iOS 真機(jī)上 app 的沙盒目錄
查看 iOS 真機(jī)上 app 的沙盒目錄比在模擬器上稍顯復(fù)雜,但也可以輕松實(shí)現(xiàn)。
1.在 Xcode 的上部導(dǎo)航欄中選擇 Window,隨后選擇 Devices。

2.選擇相應(yīng)的 Devices 后,在 Installed Apps 里面選擇要打開查看的app,點(diǎn)擊下邊的齒輪,可以用 Download Container 將沙盒文件保存到本地。

3.在本地中將沙盒文件點(diǎn)擊右鍵,選擇顯示包內(nèi)容,即可查看沙盒文件。

注意:在前面提到的環(huán)境中,上述方法只能查看 app 的 Data Container, Bundle Container 不在這一目錄下。
在沙盒目錄中,我們可以看到 Documents/、Library/、tmp/ 三個文件夾。而根據(jù) Apple 提供的文件系統(tǒng)編程指南官方文檔,我們可以對上述文件夾中應(yīng)當(dāng)存放何種文件進(jìn)行了解。以下內(nèi)容翻譯自 文件系統(tǒng)編程指南。
表1 一個 iOS app 通常使用的目錄
| 目錄名 | 描述 |
|---|---|
| appName.app | 這個是 app 的 bundle 。這個目錄包含了 app 和其所有的資源。 你沒有這個目錄的寫權(quán)限。為了防止篡改,bundle 目錄的簽名在 bundle 生成時生成。修改這個目錄會修改簽名并阻止 app 登錄。你只可以對所有保存在 bundle 里的資源有讀權(quán)限。更多內(nèi)容,參考資源編程指南(Resource Programming Guide)。 這個目錄下的內(nèi)容不會保存在 iTunes 或者 iCloud。iTunes 會對從 app Store 購買的任何應(yīng)用執(zhí)行初始同步。 |
| Documents/ | 使用這個目錄來保存用戶生成的文件。用戶可以通過文件分享功能訪問這個目錄的內(nèi)容。因此,這個目錄你應(yīng)該只放一些你希望展示給用戶的文件。 這個目錄的文件會被 iTunes 和 iCloud 備份。 |
| Documents/Inbox/ | 使用這個目錄訪問你的app打開的外部實(shí)體。特別地,郵件客戶端會將和你的app相關(guān)的附件放在你的app的這個目錄。文件交互控制器也可能會在這個文件夾存放東西。 在這個文件夾里,你的app可以讀或者刪除文件,但是不能創(chuàng)建新文件或者修改現(xiàn)有的文件。如果用戶想要在這個文件夾里嘗試去修改文件,你的app需要悄悄地把該文件移除這個文件夾。 這個目錄的文件會被 iTunes 和 iCloud 備份。 |
| Library/ | 這是一個所有非用戶數(shù)據(jù)文件的頂級目錄。你通常把文件放到多個標(biāo)準(zhǔn)子目錄下中的一個。iOS app 通常使用 application Support 和 Cache 子文件夾,你也可以創(chuàng)建自己的子文件夾。使用 Library 子文件夾來存放任何你不想被用戶看到的文件。你的app不應(yīng)該使用這些文件夾來存放用戶數(shù)據(jù)文件。 除了 Caches 子文件夾,Library 文件夾下的內(nèi)容會被 iTunes 和 iCloud 備份。 |
| tmp/ | 使用這個文件夾來存放在臨時文件,這些臨時文件在兩次打開 app 之間不一定需要保存。當(dāng)不在需要這些文件時,你的 app 需要這個文件夾中移除它們。系統(tǒng)也可能會在你的 app 不再運(yùn)行的時候清除這個文件夾。 這個目錄的文件 不會 被 iTunes 和 iCloud 備份。 |
一個 iOS app 可以在 Documents、Library 和 tmp 文件夾下建立新的文件夾。在這個目錄下你需要更好地安排文件放置的位置。
app 的文件存放的地方
為了防止 iOS 設(shè)備同步與備份的過程持續(xù)太久,注意選擇存放文件的位置。存放了大量文件的 app 會減慢 iTunes 或者 iCloud 的備份速度。這些 app 也會消耗大量的用戶可用儲存空間,這會導(dǎo)致用戶刪除 app 或者不允許 app 的數(shù)據(jù)備份到 iCloud 中。因此,保存 app 的數(shù)據(jù)需要遵循以下幾點(diǎn)準(zhǔn)則。
- 將用戶數(shù)據(jù)保存在
Documents/. 用戶數(shù)據(jù)包括所有你希望展示給用戶的文件——那些你希望用戶創(chuàng)建、引入、刪除或編輯的。對于一個繪畫 app,用戶數(shù)據(jù)包括任何用戶想要創(chuàng)建的圖片分揀。對于一個文本編輯器,用戶數(shù)據(jù)包括文本文件。音視頻 app 的用戶數(shù)據(jù)則可能包括用戶已經(jīng)下載的想要隨后觀看或收聽的文件。 - 將 app 創(chuàng)建的支持文件保存在
Library/Application support/文件夾。通常地,在這個文件夾下包括那些 app 運(yùn)行時需要使用但應(yīng)該對用戶隱藏的文件。這個文件夾也包括從 app bundle 讀取的數(shù)據(jù)文件、配置文件、模板和修改版本資源。 - 要記住在
Documents/下和Application support/文件夾在默認(rèn)情況下會被備份。你可以通過調(diào)用-[NSURL setResourceValue:forKey:error:]使用NSURLIsExcludedFromBackupKey值來防止一些文件被備份。所有可以重復(fù)創(chuàng)建或者下載的文件必須排除在備份外。特別包括那些大的多媒體文件。如果你的應(yīng)用下載音視頻文件,保證它們不在備份中。 - 將臨時數(shù)據(jù)保存在
tmp/文件夾中。臨時文件包括那些在很長一段時間內(nèi)不需要持續(xù)存在的文件。記得在使用這些文件結(jié)束后刪除它們防止它們繼續(xù)占用用戶設(shè)備的空間。系統(tǒng)會在你的 app 非運(yùn)行的時候周期性地清除這些文件,因此你不能保證這些文件在你的 app 終止后還繼續(xù)存在。 - 將數(shù)據(jù)緩存文件保存在
Library/Caches/文件夾中。緩存文件包括那些比臨時文件保存時間要長,但是沒有支持文件時間這么長的文件。通常來說,應(yīng)用在沒有緩存數(shù)據(jù)時可以正常運(yùn)行,但是在存在緩存文件時能夠提升性能。緩存文件包括(但不限于)數(shù)據(jù)庫緩存文件以及短暫的可下載內(nèi)容。需要注意的是,系統(tǒng)可能會刪除Caches/文件夾里的內(nèi)容來釋放磁盤空間,因此你的 app 需要保證這些文件是可以重新創(chuàng)建或者下載的。
計(jì)算緩存大小與刪除緩存
重要的事情說三遍:
數(shù)據(jù)無價!
數(shù)據(jù)無價!
數(shù)據(jù)無價!
在進(jìn)行數(shù)據(jù)操作時,請牢記數(shù)據(jù)無價這一原則。防止因?yàn)檎`操作將用戶寶貴的數(shù)據(jù)刪除,造成不必要的麻煩。
計(jì)算緩存大小
- (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 。
刪除緩存
再提醒一遍,刪除之前請?jiān)偃_認(rèn)刪除的文件不會影響程序的正常運(yùn)行。當(dāng)然了,如果保存 app 的數(shù)據(jù)沒有按照上述提到的準(zhǔn)則,則在審核時可能無法通過。
/*
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 將文件夾內(nèi)的所有子文件與文件夾刪除。注意:當(dāng)使用 removeItemAtPath 將刪除文件夾下所有內(nèi)容包含自身文件夾,與并使用 createDirectoryAtPath 重新創(chuàng)建該文件夾時,第二次調(diào)用 removeItemAtPath 不一定能將之前手動創(chuàng)建的文件夾刪除,造成緩存堆積。
遇到的一些問題
- 在真機(jī)調(diào)試的時候,某些數(shù)據(jù)死活刪不去,而在模擬器上沒有這個問題。考慮到可能是上一個版本遺留的文件,手動刪掉 app 再重新倒入就好了。
- 真機(jī)調(diào)試的時候,
Library/Caches/Snapshots/里的內(nèi)容刪不去。至今沒有找到解決方案。 - 如何定時清理緩存也是一個研究點(diǎn)。
寫在最后
本篇文章通過介紹沙盒系統(tǒng),查看沙盒文件,通過代碼計(jì)算緩存大小與刪除緩存四個部分對 iOS app 對文稿與數(shù)據(jù)部分進(jìn)行理解和分析。想要深入了解,可以閱讀 Apple 提供的文件系統(tǒng)編程指南官方文檔,和 SDWebImage 的 SDImageCache 部分源碼。
參考文獻(xiàn)
[1] Apple 文件系統(tǒng)編程指南官方文檔
[2] Apple API Reference NSFileManager 部分
[3] iOS查看沙盒文件圖文教程(真機(jī)+模擬器)-Darren.Von
[4] SDWebImage 源碼