實現iOS圖片等資源文件的熱更新化(三):動態的資源文件夾

来源:http://www.cnblogs.com/ios122/archive/2016/10/04/5930305.html
-Advertisement-
Play Games

此文,將嘗試動態從某個不確定的文件夾中載入資源文件.文章,會繼續完善自定義的 imageNamed 函數,併為下一篇文章鋪墊. ...


簡介

此文,將嘗試動態從某個不確定的文件夾中載入資源文件.文章,會繼續完善自定義的 imageNamed 函數,併為下一篇文章鋪墊.

這麼做的意義

正如我們經常所說的那樣,大多數情景知道做事的意義往往比做事的方法本身更有意義.意義本身,往往蘊含著目的,最終的需求一類的東西;而方法,只是我們暫時尋找的用來達到最終的目的採取的一種可行的手段.知曉意義本身的意義在於,在以後的以後,我們有可能找到更合適的方法來實現目的;也就是我們所說的,到知識的豐富性得到一定程度之後,許多人在自己的個人技能提升過程中,多少總會有那種融會貫通,一通百通的情況出現.可以肯定的是,那種醍醐灌頂的感覺,肯定不是單純的編碼行數的變化的引起的;更多的,是由於你在有意無意中關於某個編碼需求本身的意義的探尋所促成的.

具體到這裡,我們為什麼需要動態的資源文件夾呢?就目前的探討本身所透露出來的信息而言,主要是因為我們的main.bundle放在了app里,而iOS App本身的打包進去的文件,在用戶手機上是只讀的.這樣表述,有三層含義:

  1. 如果你的資源文件是放置在App ipa包里的,嘗試直接更新它,是不可能的 -- 至少對於一個native的 iOS App 是這樣;
  2. 如果你的main.bundle是從網上動態下載的,每次下載都放置到用戶文件夾特定位置,那你的確是不需要考慮過多動態資源文件夾的;
  3. 如果某一天iOS機制的發生變化,或者你為其他平臺編寫app,但是其本身的App資源文件是可寫的,那你也很可能是可以不用動態資源文件夾的;

從特定的緩存目錄讀取資源文件

從特定的緩存目錄讀取載入資源文件,可以看做動態資源文件夾的一種特殊形式,所以我們先試著處理這種單一的情況.

1.動態拼接處特定的緩存目錄

在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/

2.App第一次初始化時,將資源文件複製到特定緩存目錄

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);
    }
}

 

代碼,基本就像上面那樣,有幾個點我想著重說一下:

  1. fileExistsAtPath 判定 緩存目錄的有無來判定是否是第一次啟動.這個邏輯,在真實的補丁邏輯中,很可能是不嚴密的,後續會使用其他方式,此處夠用即可;
  2. createDirectoryAtPath 用於目錄不存在時,先構建目錄的層級結構;否則如果直接複製,很有可能會報錯的 -- 這取決於你的複製的目標目錄與已有目錄的層級差是否為1;
  3. copyItemAtPath:toPath: 的 toPath 是一個完整的且不存在的目標路徑,不一定非得與 copyItemAtPath 參數的最後一級路徑同名,此處僅為簡化處理;以後如果有需要,此函數是可以通過同時執行複製和重命名兩個操作的,如將 main.bundle 重名為 default.bundle ;
  4. 代碼最好放在 AppDelegate.m 中;
  5. 在模擬器上,你可以很容易地看到函數執行後的效果:右擊finder --> 前往文件夾 --> 輸入Xcode輸出的 緩存資源目錄.

前往文件夾 輸入Xcode輸出的 *緩存資源目錄*模擬器結果

3.從特定緩存目錄載入文件

因為目錄是特定的,我們只要每次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);

 

參考資源:


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 當某個activity變得“容易”被系統銷毀時,該activity的onSaveInstanceState就會被執行,除非該activity是被用戶主動銷毀的,例如當用戶按BACK鍵的時候。 註意上面的雙引號,何為“容易”?言下之意就是該activity還沒有被銷毀,而僅僅是一種可能性。這種可能性有 ...
  • 本章節主要為之前項目 JXHomepwner 添加照片功能(項目地址)。具體任務就是顯示一個 UIImagePickerController 對象,使用戶能夠為 JXItem 對象拍照並保存。拍攝的照片會和相應的 JXItem 對象建立關聯,當用戶進入某個 JXItem 對象的詳細視圖的時候,可以看 ...
  • 說明 JSBridge實現示例 目錄 前言 參考來源 楔子 JS實現部分 說明 實現 Android實現部分 說明 JSBridge類 實現 Callback類 實現 Webview容器關鍵代碼 實現 API 類實現 iOS實現部分 說明 WebViewJavascriptBridgeBase 實現 ...
  • 一直想弄清楚onTouchEvent,onInterceptTouchEvent,dispatchTouchEvent的執行順序,以及內部使用switch (event.getAction())中的執行順序。趁這次機會趕緊弄清楚。 重寫上面幾個方法後。我們在LogCat中看看列印的結果。 一.isO ...
  • 說明 JSBridge實現原理 目錄 前言 參考來源 前置技術要求 楔子 原理概述 簡介 url scheme介紹 實現流程 實現思路 第一步:設計出一個Native與JS交互的全局橋對象 第二步:JS如何調用Native 第三步:Native如何得知api被調用 第四步:分析url-參數和回調的格 ...
  • 基於OpenSLL的RSA加密應用(非演算法) === iOS開發中的小伙伴應該是經常用der和p12進行加密解密,而且在通常加密不止一種加密演算法,還可以加點兒鹽吧~本文章主要闡述的是在iOS中基於openSLL的RSA加密。一共有兩種方式,一種是基於p12加密解密的,還有一種是博客園官方提供的公鑰字 ...
  • 說明 Hybrid模式原生和H5交互原理 目錄 前言 參考來源 前置技術要求 楔子 Android、iOS原生和H5的基本通信機制 Android端 iOS端 原生和H5的另一種通訊方式:JSBridge 什麼是JSBridge 為什麼要用JSBridge JSBridge原理以及實現 前言 參考來 ...
  • 大家或許有遇到這個神坑,在Fragment中使用startActivityForResult能夠成功,可是在Fragment中的onActivityResult卻無法被調用。一不註意就讓人一夜愁白了頭。苦經探索(當然包括親愛的百度和谷歌),終於總結出了一些規律。 在Fragment中使用startA ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...