實現iOS圖片等資源檔的熱更新化(四): 一個最小化的補丁更新邏輯,ios資源檔

來源:互聯網
上載者:User

實現iOS圖片等資源檔的熱更新化(四): 一個最小化的補丁更新邏輯,ios資源檔
簡介

以前寫過一個補丁更新的文章,此處會做一個更精簡的最小化實現,以便於整合.為了使邏輯具有通用性,將剝離對AFNetworking和ReativeCocoa的依賴.原來的文章,可以先看這裡: http://www.ios122.com/2015/12/jspatconline/

這麼做的意義

先交代動機和意義,或許應該成為自己部落格的一個標準架構內容之一,不然以後自己需要看著,也不過是一堆乾癟的代碼.基本的邏輯圖,如上!此處,我就從簡!

從簡的原因有3:

所以說:這篇文章的意義,其實是在於簡化已有的熱更新代碼,越簡單越好維護.

基本思路

下面就一步一步來實現了.

App啟動時,判斷有無最新圖片資源

此處主要涉及到的可能的技術點:

1. 如何用基礎的網路類庫發送網路請求?

先簡單封裝一個函數來擷取,用到了block.block經常用,但到現在都記不太清形式,大都是從其他處copy下,然後改改參數.記不住,也懶得記!

- (void)fetchPatchInfo:(NSString *) urlStr completionHandler:(void (^)(NSDictionary * patchInfo, NSError * error))completionHandler{    NSURLSessionConfiguration * defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];    NSURLSession * defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];    NSURL * url = [NSURL URLWithString:urlStr];    NSURLSessionDataTask * dataTask = [defaultSession dataTaskWithURL:url                                                    completionHandler:^(NSData * data, NSURLResponse * response, NSError * error) {                                                        NSDictionary * patchInfo = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];;                                                        completionHandler(patchInfo, error);                                                    }];    [dataTask resume];}

 

基於block,調用的代碼也就很簡答了.

[self fetchPatchInfo: @"https://raw.githubusercontent.com/ios122/ios_assets_hot_update/master/res/patch_04.json" completionHandler:^(NSDictionary * patchInfo, NSError * error) {     if ( ! error) {         NSLog(@"patchInfo:%@", patchInfo);     }else     {         NSLog(@"fetchPatchInfo error: %@", error);     } }];

 

好吧,我承認AFNetworking用習慣了,好久沒用原始的網路請求的代碼了,有點low,莫怪!

2. 如何校正下載的檔案的md5值,如果你需要的話?

開頭那篇文章連結裡,有提到.核心,其實是在於下載檔案之後,md5值的計算,剩餘的就是字串比較操作了.

注意要先引入系統庫

 #include <CommonCrypto/CommonDigest.h>

 

/** *  擷取檔案的md5資訊. * *  @param path 檔案路徑. * *  @return 檔案的md5值. */-(NSString *)mcMd5HashOfPath:(NSString *)path{    NSFileManager * fileManager = [NSFileManager defaultManager];    // 確保檔案存在.    if( [fileManager fileExistsAtPath:path isDirectory:nil] )    {        NSData * data = [NSData dataWithContentsOfFile:path];        unsigned char digest[CC_MD5_DIGEST_LENGTH];        CC_MD5( data.bytes, (CC_LONG)data.length, digest );        NSMutableString * output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];        for( int i = 0; i < CC_MD5_DIGEST_LENGTH; i++ )        {            [output appendFormat:@"%02x", digest[i]];        }        return output;    }    else    {        return @"";    }}

 

3. 使用什麼儲存與擷取本機快取資源的md5等資訊?

好吧,我打算直接使用使用者設定檔,

NSString * source_patch_key = @"SOURCE_PATCH";[[NSUserDefaults standardUserDefaults] setObject:patchInfo forKey: source_patch_key];patchInfo = [[NSUserDefaults standardUserDefaults] objectForKey: source_patch_key];NSLog(@"patchInfo:%@", patchInfo);

 

補丁下載與解壓

此處主要涉及到的可能的技術點:

1. 如何基於圖片緩衝資訊來找到指定的緩衝目錄?

問題本身有些繞口,其實我想做的就是根據補丁的md5,放到不同的快取檔案夾,如補丁md5為 e963ed645c50a004697530fa596f180b,則對應放到 patch/e963ed645c50a004697530fa596f180b 檔案夾.封裝一個簡單的根據md5返回緩衝路徑的方法吧:

- (NSString *)cachePathFor:(NSString * )patchMd5{    NSArray * LibraryPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);    NSString * cachePath = [[[LibraryPaths objectAtIndex:0] stringByAppendingFormat:@"/Caches/patch"] stringByAppendingPathComponent:patchMd5];    return cachePath;}

 

使用時,類似這樣:

NSString * urlStr = [patchInfo objectForKey: @"url"];[weak_self downloadFileFrom:urlStr completionHandler:^(NSURL * location, NSError * error) {    if (error) {        NSLog(@"download file url:%@  error: %@", urlStr, error);        return;    }    NSString * cachePath = [weak_self cachePathFor: [patchInfo objectForKey:@"md5"]];    NSLog(@"location:%@ cachePath:%@",location, cachePath);}];

 

2. 如何解壓檔案到指定目錄?

如果需要安裝 CocoaPods ,建議使用 brew:

brew install CocoaPods

 

解壓本身推薦 SSZipArchive 庫,一行代碼搞定:

[SSZipArchive unzipFileAtPath:location.path toDestination: patchCachePath overwrite:YES password:nil error:&error];

 

3. 在什麼時候更新本地的緩衝資源的相關資訊?

建議是在下載並解壓資源檔到指定緩衝目錄後,再更新補丁的相關緩衝資訊,因為這個資訊,讀取圖片時,也是需要的.如果刪除某個補丁,按照目前的設計,一種比較偷懶的方案就是,在伺服器上放上一個新的空資源檔就可以了.

NSString * source_patch_key = @"SOURCE_PATCH";[[NSUserDefaults standardUserDefaults] setObject:patchInfo forKey: source_patch_key];

 

讀取圖片功能擴充

此處主要涉及到的可能的技術點:

1. 如何用基礎的網路類庫下載檔案?

依然是要封裝一個簡單函數,下載完成後,通過block傳出檔案臨時的儲存位置:

-(void) downloadFileFrom:(NSString * ) urlStr completionHandler: (void (^)(NSURL *location, NSError * error)) completionHandler{    NSURL * url = [NSURL URLWithString:urlStr];    NSURLSessionConfiguration * defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];    NSURLSession * defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate:self delegateQueue: [NSOperationQueue mainQueue]];    NSURLSessionDownloadTask * downloadTask =[ defaultSession downloadTaskWithURL:url                                                                completionHandler:^(NSURL * location, NSURLResponse * response, NSError * error)                                              {                                                  completionHandler(location,error);                                              }];    [downloadTask resume];}

 

2. 如何判斷bundle中是否含有某檔案?

可以使用 fileExistsAtPath,但其實使用 -pathForResource: ofType: 就夠了,因為找不到資源問加你時,它返回nil,所以我們直接調用它,然後判斷返回是否為 nil 即可:

NSString * imgPath = [mainBundle pathForResource:imgName ofType:@"png"];

 

3. 將代碼如何與原有的imageNamed:邏輯合并?

不需要初始複製到緩衝目錄 + 初始請求最新的資源補丁資訊 + 代碼遷移合并 + 介面最佳化

相對完整的邏輯代碼

注意,按照目前的設計,就不需要初始把原來ipa中的bundle複製到緩衝目錄了;當緩衝目錄中沒有相關資源時,會自動嘗試從ipa中的bundle讀取,bundle約定統一使用 main.bundle 來簡化操作,

類目,對外暴露兩個方法:

#import <UIKit/UIKit.h>@interface UIImage (imageNamed_bundle_)/* load img smart .*/+ (UIImage *)yf_imageNamed:(NSString *)imgName;/* smart update for patch */+ (void)yf_updatePatchFrom:(NSString *) pathInfoUrlStr;@end

 

App啟動時,或在其他合適的地方,要注意檢查有無更新:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    // Override point for customization after application launch.    /* fetch pathc info every time */    NSString * patchUrlStr = @"https://raw.githubusercontent.com/ios122/ios_assets_hot_update/master/res/patch_04.json";    [UIImage yf_updatePatchFrom: patchUrlStr];    return YES;}

 

內部實現,最佳化了許多,但也算不上複雜:

#import "UIImage+imageNamed_bundle_.h"#import <SSZipArchive.h>@implementation UIImage (imageNamed_bundle_)+ (NSString *)yf_sourcePatchKey{    return @"SOURCE_PATCH";}+ (void)yf_updatePatchFrom:(NSString *) pathInfoUrlStr{    [self yf_fetchPatchInfo: pathInfoUrlStr       completionHandler:^(NSDictionary *patchInfo, NSError *error) {           if (error) {               NSLog(@"fetchPatchInfo error: %@", error);               return;           }           NSString * urlStr = [patchInfo objectForKey: @"url"];           NSString * md5 = [patchInfo objectForKey:@"md5"];           NSString * oriMd5 = [[[NSUserDefaults standardUserDefaults] objectForKey: [self yf_sourcePatchKey]] objectForKey:@"md5"];           if ([oriMd5 isEqualToString:md5]) { // no update               return;           }           [self yf_downloadFileFrom:urlStr completionHandler:^(NSURL *location, NSError *error) {               if (error) {                   NSLog(@"download file url:%@  error: %@", urlStr, error);                   return;               }               NSString * patchCachePath = [self yf_cachePathFor: md5];               [SSZipArchive unzipFileAtPath:location.path toDestination: patchCachePath overwrite:YES password:nil error:&error];               if (error) {                   NSLog(@"unzip and move file error, with urlStr:%@ error:%@", urlStr, error);                   return;               }               /* update patch info. */               NSString * source_patch_key = [self yf_sourcePatchKey];               [[NSUserDefaults standardUserDefaults] setObject:patchInfo forKey: source_patch_key];           }];       }];}+ (NSString *)yf_relativeCachePathFor:(NSString *)md5{    return [@"patch" stringByAppendingPathComponent:md5];}+ (UIImage *)yf_imageNamed:(NSString *)imgName{    NSString * bundleName = @"main";    /* cache dir */    NSString * md5 = [[[NSUserDefaults standardUserDefaults] objectForKey: [self yf_sourcePatchKey]] objectForKey:@"md5"];    NSString * relativeCachePath = [self yf_relativeCachePathFor: md5];    return [self yf_imageNamed: imgName bundle:bundleName cacheDir: relativeCachePath];}+ (UIImage *)yf_imageNamed:(NSString *)imgName bundle:(NSString *)bundleName cacheDir:(NSString *)cacheDir{    NSArray * LibraryPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);    bundleName = [NSString stringWithFormat:@"%@.bundle",bundleName];    NSString * ipaBundleDir = [NSBundle mainBundle].resourcePath;    NSString * cacheBundleDir = ipaBundleDir;    if (cacheDir) {        cacheBundleDir = [[[LibraryPaths objectAtIndex:0] stringByAppendingFormat:@"/Caches"] stringByAppendingPathComponent:cacheDir];    }    imgName = [NSString stringWithFormat:@"%@@3x",imgName];    NSString * bundlePath = [cacheBundleDir stringByAppendingPathComponent: bundleName];    NSBundle * mainBundle = [NSBundle bundleWithPath:bundlePath];    NSString * imgPath = [mainBundle pathForResource:imgName ofType:@"png"];    /* try load from ipa! */    if ( ! imgPath && ! [ipaBundleDir isEqualToString: cacheBundleDir]) {        bundlePath = [ipaBundleDir stringByAppendingPathComponent: bundleName];        mainBundle = [NSBundle bundleWithPath:bundlePath];        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;}+ (void)yf_fetchPatchInfo:(NSString *) urlStr completionHandler:(void (^)(NSDictionary * patchInfo, NSError * error))completionHandler{    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];    NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];    NSURL * url = [NSURL URLWithString:urlStr];    NSURLSessionDataTask * dataTask = [defaultSession dataTaskWithURL:url                                                    completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {                                                        NSDictionary * patchInfo = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];;                                                        completionHandler(patchInfo, error);                                                    }];    [dataTask resume];}+ (void) yf_downloadFileFrom:(NSString * ) urlStr completionHandler: (void (^)(NSURL *location, NSError * error)) completionHandler{    NSURL * url = [NSURL URLWithString:urlStr];    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];    NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate:nil delegateQueue: [NSOperationQueue mainQueue]];    NSURLSessionDownloadTask * downloadTask =[ defaultSession downloadTaskWithURL:url                                                                completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error)                                              {                                                  completionHandler(location,error);                                              }];    [downloadTask resume];}+ (NSString *)yf_cachePathFor:(NSString * )patchMd5{    NSArray * LibraryPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);    NSString * cachePath = [[[LibraryPaths objectAtIndex:0] stringByAppendingFormat:@"/Caches"] stringByAppendingPathComponent:[self yf_relativeCachePathFor: patchMd5]];    return cachePath;}@end

 

現在,載入圖片的代碼更簡單了:

UIImage * image = [UIImage yf_imageNamed:@"sub/sample"];self.sampleImageView.image = image;

 

如果熱更新生效,運行看到的應該是一個鎚子圖片:

後記

我覺得,這篇文章最大的特點是,完整記錄了一次最佳化解決問題的過程;範例程式碼看起來前後有些不太統一,是因為: 我不是先有了方案再寫部落格,而是藉助部落格本身來梳理思路,簡化邏輯!如此,寫部落格,就不單單是一個耗時的分享知識的過程,更成為了一個協助自己思考的有力工具!贊!!!

參考資源:
  • 本節內容完整可執行Xcode工程代碼,不到100k
  • 系列文章,專屬github項目
  • iOS NSURLSession Example (HTTP GET, POST, Background Downlads )
  • 價值100W的經驗分享: 基於JSPatch的iOS應用線上Bug的即時修複方案,附源碼.
  • ZipArchive is a simple utility class for zipping and unzipping files on iOS and Mac.
  • pod 的安裝和使用

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.