实现iOS图片等资源文件的热更新化(三):动态的资源文件夹

简介

此文,将尝试动态从某个不确定的文件夹中加载资源文件.文章,会继续完善自定义的 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输出的 *缓存资源目录*
输入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);

参考资源:

本节内容完整可执行Xcode工程代码,只有100k

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容