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