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

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 是一个自制的动态库;右边是项目文件夹中的文件层级。

三、SDK 开发中图片资源读取问题_第1张图片
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 开发中图片资源读取问题)