此文,將嘗試動態從某個不確定的文件夾中加載資源文件.文章,會繼續完善自定義的 imageNamed 函數,並為下一篇文章鋪墊.
正如我們經常所說的那樣,大多數情景知道做事的意義往往比做事的方法本身更有意義.意義本身,往往蘊含著目的,最終的需求一類的東西;而方法,只是我們暫時尋找的用來達到最終的目的采取的一種可行的手段.知曉意義本身的意義在於,在以後的以後,我們有可能找到更合適的方法來實現目的;也就是我們所說的,到知識的豐富性得到一定程度之後,許多人在自己的個人技能提升過程中,多少總會有那種融會貫通,一通百通的情況出現.可以肯定的是,那種醍醐灌頂的感覺,肯定不是單純的編碼行數的變化的引起的;更多的,是由於你在有意無意中關於某個編碼需求本身的意義的探尋所促成的.
具體到這裡,我們為什麼需要動態的資源文件夾呢?就目前的探討本身所透露出來的信息而言,主要是因為我們的main.bundle放在了app裡,而iOS App本身的打包進去的文件,在用戶手機上是只讀的.這樣表述,有三層含義:
如果你的資源文件是放置在App ipa包裡的,嘗試直接更新它,是不可能的 – 至少對於一個native的 iOS App 是這樣; 如果你的main.bundle是從網上動態下載的,每次下載都放置到用戶文件夾特定位置,那你的確是不需要考慮過多動態資源文件夾的; 如果某一天iOS機制的發生變化,或者你為其他平台編寫app,但是其本身的App資源文件是可寫的,那你也很可能是可以不用動態資源文件夾的;從特定的緩存目錄讀取加載資源文件,可以看做動態資源文件夾的一種特殊形式,所以我們先試著處理這種單一的情況.
在iOS App中, 固定 的緩存目錄和 特定 的緩存目錄,還是有區別的.主要是因為真機上iOS App每次啟動時,其對應的文件目錄是動態變化的.也就是說,我們以後如果有存儲文件路徑的需求,一定要記住只能存儲文件相對於程序沙盒主目錄 NSHomeDirectory 的相對路徑.順便說一句,主目錄的程序主目錄的可見子目錄有3個,分別是: Documents , Library , tmp ,具體介紹,可參考博文: iOS沙盒文件讀寫
Documents:蘋果建議將程序創建產生的文件以及應用浏覽產生的文件數據保存在該目錄下,iTunes備份和恢復的時候會包括此目錄 Library:存儲程序的默認設置或其它狀態信息; Library/Caches:存放緩存文件,保存應用的持久化數據,用於應用升級或者應用關閉後的數據保存,不會被itunes同步,所以為了減少同步的時間,可以考慮將一些比較大的文件而又不需要備份的文件放到這個目錄下。 tmp:提供一個即時創建臨時文件的地方,但不需要持久化,在應用關閉後,該目錄下的數據將刪除,也可能系統在程序不運行的時候清除。
現在我們的資源目錄,將假定固定放在相對目錄 Library/Caches/patch 中,其名為 main.bundle
那麼在需要時,我們就可以這樣訪問到我們的資源文件夾:
NSArray * LibraryPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); NSString * cacheBundleDir = [[LibraryPaths objectAtIndex:0] stringByAppendingFormat:@"/Caches/Patch/"]; NSLog(@"緩存資源目錄: %@", cacheBundleDir); // 模擬器示例輸出: 緩存資源目錄: /Users/yanfeng/Library/Developer/CoreSimulator/Devices/930159D0-850F-43CE-88D2-08BE9D4A7E7F/data/Containers/Data/Application/EE3A92AB-2DBE-44C5-9103-11BAC7AECE15/Library/Caches/Patch/
NSString * bundleName = @"main.bundle"; NSError * err = nil; NSFileManager * defaultManager = [NSFileManager defaultManager]; if ( ! [defaultManager fileExistsAtPath:cacheBundleDir]) { [defaultManager createDirectoryAtPath:cacheBundleDir withIntermediateDirectories:YES attributes:nil error: &err]; if(err){ NSLog(@"初始化目錄出錯:%@", err); } NSString * defaultBundlePath = [[NSBundle mainBundle].resourcePath stringByAppendingPathComponent: bundleName]; NSString * cacheBundlePath = [cacheBundleDir stringByAppendingPathComponent:bundleName]; [defaultManager copyItemAtPath: defaultBundlePath toPath:cacheBundlePath error: &err]; if(err){ NSLog(@"復制初始資源文件出錯:%@", err); } }
代碼,基本就像上面那樣,有幾個點我想著重說一下:
fileExistsAtPath 判定 緩存目錄的有無來判定是否是第一次啟動.這個邏輯,在真實的補丁邏輯中,很可能是不嚴密的,後續會使用其他方式,此處夠用即可; createDirectoryAtPath 用於目錄不存在時,先構建目錄的層級結構;否則如果直接復制,很有可能會報錯的 – 這取決於你的復制的目標目錄與已有目錄的層級差是否為1; copyItemAtPath:toPath: 的 toPath 是一個完整的且不存在的目標路徑,不一定非得與 copyItemAtPath 參數的最後一級路徑同名,此處僅為簡化處理;以後如果有需要,此函數是可以通過同時執行復制和重命名兩個操作的,如將 main.bundle 重名為 default.bundle ; 代碼最好放在 AppDelegate.m 中; 在模擬器上,你可以很容易地看到函數執行後的效果:右擊finder –> 前往文件夾 –> 輸入Xcode輸出的 緩存資源目錄.
因為目錄是特定的,我們只要每次App啟動後,根據相對路徑動態獲取絕對路徑,進而拿到 緩存目錄中 main.bundle 資源包路徑,然後就可以使用已有的方法,從 bundle 裡取圖片即可:
NSArray * LibraryPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); NSString * cacheBundleDir = [[LibraryPaths objectAtIndex:0] stringByAppendingFormat:@"/Caches/Patch/"]; NSString * bundleName = @"main.bundle"; NSString * imgName = @"sample@3x"; NSString * bundlePath = [cacheBundleDir stringByAppendingPathComponent: bundleName]; NSBundle * cacheMainBundle = [NSBundle bundleWithPath:bundlePath]; NSString * imgPath = [cacheMainBundle pathForResource:imgName ofType:@"png"]; UIImage * image = [UIImage imageWithContentsOfFile: imgPath]; self.sampleImageView.image = image;
這裡,主要是和實現iOS圖片等資源文件的熱更新化(二):自定義的動態 imageNamed的類目方法結合擴展下,使原來的類目擴展支持從動態的緩存目錄讀取bundle,思路本身也很簡單,只要更改下用於確定bundle位置處的代碼即可:
+ (UIImage *)imageNamed:(NSString *)imgName bundle:(NSString *)bundleName cacheDir:(NSString *)cacheDir { NSArray * LibraryPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); NSString * cacheBundleDir = [NSBundle mainBundle].resourcePath; if (cacheDir) { cacheBundleDir = [[[LibraryPaths objectAtIndex:0] stringByAppendingFormat:@"/Caches"] stringByAppendingPathComponent:cacheDir]; } bundleName = [NSString stringWithFormat:@"%@.bundle",bundleName]; imgName = [NSString stringWithFormat:@"%@@3x",imgName]; NSString * bundlePath = [cacheBundleDir stringByAppendingPathComponent: bundleName]; NSBundle * mainBundle = [NSBundle bundleWithPath:bundlePath]; NSString * imgPath = [mainBundle pathForResource:imgName ofType:@"png"]; UIImage * image; static NSString * model; if (!model) { model = [[UIDevice currentDevice]model]; } if ([model isEqualToString:@"iPad"]) { NSData * imageData = [NSData dataWithContentsOfFile: imgPath]; image = [UIImage imageWithData:imageData scale:2.0]; }else{ image = [UIImage imageWithContentsOfFile: imgPath]; } return image; }
原來的從 ipa 包中加載 資源文件的邏輯可以基於此方法重寫:
+ (UIImage *)imageNamed:(NSString *)imgName bundle:(NSString *)bundleName { return [self imageNamed:imgName bundle:bundleName cacheDir:nil]; }
+ (UIImage )imageNamed:(NSString )imgName bundle:(NSString )bundleName cacheDir:(NSString )cacheDir 方法中的 cacheDir 也是支持多級目錄的,如:
UIImage * image = [UIImage imageNamed:@"sub/sample" bundle:@"main" cacheDir:@"patch/default"]; self.sampleImageView.image = image;
注意,此時初始復制到緩存目錄的邏輯,也是適當對應子目錄變更下:
NSArray * LibraryPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); NSString * cacheBundleDir = [[LibraryPaths objectAtIndex:0] stringByAppendingFormat:@"/Caches/Patch/default/"]; NSLog(@"緩存資源目錄: %@", cacheBundleDir);