实现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输出的 缓存资源目录.

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

参考资源:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏施炯的IoT开发专栏

PhoneFinder--寻找丢失的手机

    手机丢了怎么办?那就打电话给手机,如果运气好的话,捡到的好心人能够把手机还给你。如果手机是被偷的,那就没有办法了,即使手机开着,估计小偷也不会接电话。当...

2684
来自专栏進无尽的文章

扒虫篇-Bug日志 Ⅰ

之前在集成调试 AsReader的时候,遇到的bug,是一家日本企业生产的产品,官方文档比较简单而且还不写清楚,表面上看是报的不兼容 64位模拟器的错误,我用...

782
来自专栏liuchengxu

怎么写一个好的 Git commit message

网络上很多关于 commit message 的想法都来源于 tpope,在他看来,一个好的 Git commit messge 应该是这样的:

612
来自专栏carven

第一篇博客---hexo博客建成

  在国庆期间,时间很充裕,刚好阿里云有一个云翼计划,大学生9.9一个月就可以拥有一台自己的服务器。我趁自己有点闲钱就入手了一个。

522
来自专栏深度学习思考者

一篇关于视频下载的技术贴(源码)

前言: 在GitHub,用户可以十分轻易地找到海量的开源代码。本文就针对很常用的视频下载任务,给出了最简单粗暴的下载方式。 任务目标: 使用视频下载的开源项目,...

1877
来自专栏王大锤

iOS地理围栏技术的应用

3466
来自专栏FreeBuf

帮女神修手机的意外发现:隐匿在iOS文件系统中的隐私信息

说明: 1. 本文仅供安全学习及教学用途,提及的部分技术带有攻击性,请确保合法使用。 2. “这些都不是我干的,我就负责说出来。” 3. 图片仅供参考。 4. ...

3159
来自专栏JadePeng的技术博客

Html 5 video/audio 格式转换 ogg

Html5 开始支持video和audio标签,但是各个浏览器支持的格式不一样,见下图 Codec support in modern desktop brow...

40910
来自专栏DannyHoo的专栏

如何使NSLog只在Debug模式下有效

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010105969/article/details/...

542
来自专栏進无尽的文章

扒虫篇-崩溃日志解读及Crash收集

崩溃是让发人员比较头痛的事情,app崩溃了,说明代码写的有问题,这时如何快速定位到崩溃的地方很重要。调试阶段是比较容易找到出问题的地方的,但是已经上线的app并...

661

扫码关注云+社区