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

简介

此文,将尝试动态从某个不肯定的文件夹中加载资源文件.文章,会继续完善自定义的 imageNamed 函数,并为下一篇文章铺垫.ios

这么作的意义

正如咱们常常所说的那样,大多数情景知道作事的意义每每比作事的方法自己更有意义.意义自己,每每蕴含着目的,最终的需求一类的东西;而方法,只是咱们暂时寻找的用来达到最终的目的采起的一种可行的手段.知晓意义自己的意义在于,在之后的之后,咱们有可能找到更合适的方法来实现目的;也就是咱们所说的,到知识的丰富性获得必定程度以后,许多人在本身的我的技能提高过程当中,多少总会有那种融会贯通,一通百通的状况出现.能够确定的是,那种醍醐灌顶的感受,确定不是单纯的编码行数的变化的引发的;更多的,是因为你在有意无心中关于某个编码需求自己的意义的探寻所促成的.git

具体到这里,咱们为何须要动态的资源文件夹呢?就目前的探讨自己所透露出来的信息而言,主要是由于咱们的main.bundle放在了app里,而iOS App自己的打包进去的文件,在用户手机上是只读的.这样表述,有三层含义:github

  1. 若是你的资源文件是放置在App ipa包里的,尝试直接更新它,是不可能的 -- 至少对于一个native的 iOS App 是这样;
  2. 若是你的main.bundle是从网上动态下载的,每次下载都放置到用户文件夹特定位置,那你的确是不须要考虑过多动态资源文件夹的;
  3. 若是某一天iOS机制的发生变化,或者你为其余平台编写app,可是其自己的App资源文件是可写的,那你也极可能是能够不用动态资源文件夹的;

从特定的缓存目录读取资源文件

从特定的缓存目录读取加载资源文件,能够看作动态资源文件夹的一种特殊形式,因此咱们先试着处理这种单一的状况.缓存

1.动态拼接处特定的缓存目录

在iOS App中, 固定 的缓存目录和 特定 的缓存目录,仍是有区别的.主要是由于真机上iOS App每次启动时,其对应的文件目录是动态变化的.也就是说,咱们之后若是有存储文件路径的需求,必定要记住只能存储文件相对于程序沙盒主目录 NSHomeDirectory 的相对路径.顺便说一句,主目录的程序主目录的可见子目录有3个,分别是: Documents , Library , tmp ,具体介绍,可参考博文: iOS沙盒文件读写app

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

代码,基本就像上面那样,有几个点我想着重说一下:.net

  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 里取图片便可:code

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位置处的代码便可:orm

+ (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

相关文章
相关标签/搜索