全文約 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 源碼