几个重要的API
获取Bundle
Bundle是一个容器概念,NSBundle 对象不仅仅指我们可见的 XXX.bundle 文件,framework 也是属于 bundle 范畴。
读取图片的 API
系统提供了两类方法:
- 从Bundle中读取图片
- 从指定文件路径读取图片
这两类方法的本质区别在于,前者会在内存中缓存图片的二进制文件,后者不会。
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的图片资源。
cocopods 中的图片资源引用方式分析
这里的 cocopods 指的是私有库,也就是我们需要开发的组件或者SDK。
我们知道,SDK不能独立运行,需要借助一个 APP project。
我们在 SDK 中使用图片资源,按照图片的最终位置存在来分,图片资源只能存在于两个位置:
- 直接存放在 APP 的 main bundle 下面
- APP 的 main bundle 下的 其他 bundle 中
我们开发过程中,每个 SDK 我们都会独立使用一个 bundle 来存放我们的资源图片,这样方便管理,也可以避免和外部图片冲突的问题。
下面来描述整个开发流程。
编写 podspec 文件
以 WBDynamic.podspec
这个仓库为例,来演示如何在 framework 中使用图片资源。
我们一般按照如下的方式来索引资源文件。
s.resource_bundles = {
'WBDynamic' => ['WBDynamic/Assets/*.png'],
}
执行 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];
动态库和静态库通用。