三、SDK 开发中图片资源读取问题

作者: TankXie | 来源:发表于2019-02-22 15:56 被阅读12次

    1. 基本认识

    1.1 几个重要的API

    获取 bundle 的 API

    NSBundle官方文档

    理解 bundle 的概念,它就是一个容器概念,NSBundle 对象不仅仅指我们可见的 XXX.bundle 文件,framework 也是属于 bundle 范畴。

    读取图片的 API

    UIImage 官方文档

    苹果官方提供的读取图片资源的方法

    系统提供了两类方法,可以让我们读取图片:

    • 从bundle中读取图片
    • 从指定的文件路径读取图片

    这两类方法的本质区别在于,前者会在内存中缓存图片的二进制文件,后者不会,所以在使用的时候,这个可以按需选择。

    从 bundle 中读取图片

    系统提供了两个从 bundle 中获取图片的方法:

    + imageNamed:inBundle:compatibleWithTraitCollection:
    + imageNamed:
    

    我们最常用的 + imageNamed: ,从 main bundle 中读取图片。

    对于其它 bundle 中的图片资源,我们需要使用上面的第一个方法,指定图片资源所在的 bundle。

    从指定的文件路径读取图片

    如果我们知道图片资源的文件路径,我们可以通过如下方法来读取图片:

    imageWithContentsOfFile:
    

    传入的参数是图片的路径。

    1.2 cocopods 相关基础

    cocopods 中资源使用文章。

    2. APP 工程中资源文件的读取方法

    SDK 开发中,我们最常用的场景是,读取图片将其存在 UIImage 对象中,在某个地方显示出来。

    所以,这里我们介绍一下,将图片读取到 UIImage 对象中的一些基础知识。

    本文演示了,我们如何以下四种场景下,图片的读取方式:

    • 读取 project 下的图片,如图中的编号1
    • 读取 project 下文件夹中的图片,如图中的编号2
    • 读取 Assets.xcassets 中的图片,如图中的编号3
    • 读取 动态库 WBDynamic.framework 中的图片,如图中的编号4

    下图左边是 project 的文件目录结构,其中,WBDynamic.framework 是一个自制的动态库;右边是项目文件夹中的文件层级。

    image.png

    我们 build 工程,查看输出的 WBDemo.app 文件,其中的文件目录结构如下:

    ├── 1.png
    ├── 2.png
    ├── Assets.car
    ├── Base.lproj
    │   ├── LaunchScreen.storyboardc
    │   └── Main.storyboardc
    ├── Frameworks
    │   └── WBDynamic.framework
    │       ├── Info.plist
    │       ├── WBDynamic
    │       ├── WBDynamic.bundle
    │       └── _CodeSignature
    ├── Info.plist
    ├── PkgInfo
    ├── WBDemo
    └── _CodeSignature
        └── CodeResources
    

    我们发现:

    • 编号1、编号2.编号3中的图片资源,最终都会在 main bundle 下
    • 动态库单独放在 Framewokrs这个目录下面,其中编号为4 的图片资源放在 WBDynamic.framework/WBDynamic.bundle 这个路径下面

    我们先演示从 main bundle 中读取图片的情况。

    基本步骤如下:

    • 获取到图片资源所在的 bundle
    • 使用 imageNamed:inBundle:compatibleWithTraitCollection: 方法获取 UIImage 对象
    - (UIImage *)imageFromBundle{
        NSBundle *bundle = [NSBundle mainBundle];
        return [UIImage imageNamed:@"1.png"
                          inBundle:bundle
     compatibleWithTraitCollection:nil];
    }
    

    对于 main bundle 中的图片,可以直接使用 [UIImage imageNamed:@"1.png"]; 来读取。

    对于动态库中的图片,我们使用另外一种方式来演示,也就是通过图片的路径来读取图片。

    基本步骤如下:

    • 拼接图片的文件路径
    • 使用 imageWithContentsOfFile: 方法来获取 UIImage 对象
    - (UIImage *) imageFromPath{
        NSString *mainBundlePath = [[NSBundle mainBundle] bundlePath];
        NSString *imgPath = [mainBundlePath stringByAppendingString:@"/Frameworks/WBDynamic.framework/WBDynamic.bundle/1.png"];
        return [UIImage imageWithContentsOfFile:imgPath];
    }
    
    

    通过测试,我们可以正常读取到上述编号为1、2、3、4的图片资源。

    2. cocopods 中的图片资源引用方式分析

    这里的 cocopods 指的是私有库,也就是我们需要开发的组件或者SDK。

    我们知道,SDK不能独立运行,需要借助一个 APP project。

    我们在 SDK 中使用图片资源,按照图片的最终位置存在来分,图片资源只能存在于两个位置:

    • 直接存放在 APP 的 main bundle 下面
    • APP 的 main bundle 下的 其他 bundle 中

    我们开发过程中,每个 SDK 我们都会独立使用一个 bundle 来存放我们的资源图片,这样方便管理,也可以避免和外部图片冲突的问题。

    下面来描述整个开发流程。

    2.1 编写 podspec 文件

    WBDynamic.podspec 这个仓库为例,来演示如何在 framework 中使用图片资源。

    我们一般按照如下的方式来索引资源文件。

      s.resource_bundles = {
          'WBDynamic' => ['WBDynamic/Assets/*.png'],
      }
    

    2.2 执行 pod install 的变化

    在使用 WBDynamic 的地方,执行 pod install 命令之后,APP 工程会发生一些变化,这个变化和 use_frameworks! 这个标记有关系。

    由于我们在前面已经知道了,使用 use_frameworks! 这个标记,APP 项目会以动态库的方式集成 WBDynamic,否则,就会以静态库的方式集成。

    下面,我们来各自分析一下,这两种情况下资源的引用情况。

    动态库方式集成

    podfile 中添加 use_frameworks!,运行 pod install,buid 工程,查看 WBDynamic_Example.app 的文件结构,如下所示。

    ├── Base.lproj
    │   ├── LaunchScreen.storyboardc
    │   └── Main.storyboardc
    ├── Frameworks
    │   └── WBDynamic.framework
    │       ├── Info.plist
    │       ├── WBDynamic
    │       ├── WBDynamic.bundle
    │       │   ├── 1.png
    │       │   ├── 2.png
    │       │   └── Info.plist
    │       └── _CodeSignature
    │           └── CodeResources
    ├── Info.plist
    ├── PkgInfo
    ├── WBDynamic_Example
    ├── _CodeSignature
    │   └── CodeResources
    └── en.lproj
        └── InfoPlist.strings
    

    我们从中精简出资源相关的结构:

    ├── Frameworks
    │   └── WBDynamic.framework(动态库)
    │       ├── WBDynamic(动态库可执行文件)
    │       └── WBDynamic.bundle(动态库中的资源文件bundle)
    │           ├── 1.png
    │           └── 2.png
    └── WBDynamic_Example (app 可执行文件)
    

    也就说,我们需要按照上面所示的路径读取资源文件。

    要读取到图片,现在,冒在脑海中有两个思路:

    • 思路一:通过 main bundle 来定位 WBDynamic.bundle ,其相对路径为 ./Frameworks/WBDynamic.framework/WBDynamic.bundle,也就是我们上面示例演示的,这种思路不好,我们会在下一节中讲述原因
    • 思路二:通过可执行文件 WBDynamic 来定位,SDK 中的代码最终会打包到 WBDynamic 中,WBDynamic 的位置也就是当前 class 的位置,这里我们演示这种思路

    我们的做法如下:

    // [NSBundle bundleForClass:[self class]] 获取可执行文件的路径
    NSString *bundlePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"WBDynamic" ofType:@"bundle"];
    NSBundle *bundle = [NSBundle bundleWithPath:bundlePath];
    UIImage *img = [UIImage imageNamed:@"1.png" inBundle:bundle compatibleWithTraitCollection:nil];
    
    

    整个流程我们可以分解为以下几个步骤:

    • WBDynamic.bundle 和 可执行文件WBDynamic 在同一级目录下面,我们可以通过可执行文件的路径来定位 WBDynamic.bundle 的路径
    • 通过 bundle 路径获取 NSBundle 对象
    • 从 bundle 中获取图片资源

    测试通过,我们可以正常获取图片。

    当然,也还有另一种实现方法,就是通过图片的路径来获取图片,实现如下:

    NSString *bundlePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"WBDynamic" ofType:@"bundle"];
    NSBundle *bundle = [NSBundle bundleWithPath:bundlePath];
        
    NSString *imgPath = [bundle pathForResource:@"1" ofType:@"png"];
    UIImage *img = [[UIImage imageWithContentsOfFile:imgPath] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
    

    这两种方法的区别在前面讲了,一个缓存图片,一个不缓存,按需使用。

    静态库方式集成

    podfile 中注释掉 use_frameworks!,运行 pod install,buid 工程,查看 WBDynamic_Example.app 的文件结构,如下所示。

    ├── Base.lproj
    │   ├── LaunchScreen.storyboardc
    │   └── Main.storyboardc
    ├── Info.plist
    ├── PkgInfo
    ├── WBDynamic.bundle
    │   ├── 1.png
    │   ├── 2.png
    │   └── Info.plist
    ├── WBDynamic_Example
    ├── _CodeSignature
    │   └── CodeResources
    └── en.lproj
        └── InfoPlist.strings
    

    我们发现,以静态库的形式集成 SDK,cocopods 会自动在 APP 的 main bundle 中生成一个 WBDynamic.bundle

    这里我们也有两个思路:

    • 思路一:WBDynamic.bundle 在 main bundle 中,我们通过相对于 main bundle 的路径,来获取 WBDynamic.bundle,其相对路径为./WBDynamic.bundle
    • 思路二:SDK 中的代码打包到静态库中,静态库被打包到 可执行文件 WBDynamic_Example 中去了,这就和动态库的思路二相同了

    总结

    所以,我们在SDK开发的时候,我们读取图片的最佳方式如下:

    NSString *bundlePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"WBDynamic" ofType:@"bundle"];
    NSBundle *bundle = [NSBundle bundleWithPath:bundlePath];
    UIImage *img = [UIImage imageNamed:@"1.png" inBundle:bundle compatibleWithTraitCollection:nil];
    

    动态库和静态库通用。

    相关文章

      网友评论

        本文标题:三、SDK 开发中图片资源读取问题

        本文链接:https://www.haomeiwen.com/subject/urpeyqtx.html