iOS SDK(二):Bundle
iOS SDK开发系列:
iOS SDK(一):静态库、动态库创建&接口测试
iOS SDK(二):Bundle
...
待更新..
一、回顾
回顾上一篇,你可以理解并认识到Bundle在 SDK 中的作用:用来存放资源文件。SDKDemo是使用SDK的工程,由图可见,.a / .framework 、 和 .bundle需要导入工程才能使用。
iOS SDK工程关系.png
本篇来介绍下,什么是Bundle,如何读取Bundle中的资源。
二、什么是bundle
独立的物理空间,存放各种资源。
App开发与SDK开发
SDK开发中,我们把工程分为 SDK 工程 和 Demo 工程
如果是常规的App开发,我们只会接触到app 工程 ,也就是本文中的Demo 工程 ,也就是有启动入口的能够独立运行的二进制程序。对于Mach-O Type中的Executable类型。
而 SDK工程 我们开发编译出 .a / .framework ,的二进制库。而 .bundle,包含了各种资源文件(图片、编译过的二进制文件、故事板文件、SSL证书等),使用时需要导入给app 工程, SDK工程内部需要实现读取这个bundle中的资源并使用其中的资源。
理清了App开发与SDK开发的区别,下面介绍不同开发读取资源的不同方式。
App开发中
在app 工程中,我们将图片放置在工程目录中,可以通过如下方式获取到图片资源:
NSBundle *bundle = [NSBundle mainBundle];
// 或者:
// NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *imagePath = [bundle pathForResource:@"imageName.png" ofType:nil];
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
imagePath的路径是这样的:...Demo.app/imageName.png
可以看出 无论是[NSBundle mainBundle]还是[NSBundle bundleForClass:[self class]]指向的bundle都是Demo.app。我们可以把mainBundle 理解为 **.app的内部。
SDK工程中
对于 SDK 工程 读取 SDK.bundle 的资源内容,相当于...Demo.app/imageName.png中间多嵌套一层:
...Demo.app/SDK.bundle/imageName.png
也就是说,我们读取bundle中的资源,其路径是:mainBundle的路径 + 自定义bundle名称 + 文件名。
如下:
NSBundle *mainBundle = [NSBundle mainBundle];
NSString *sdkBundlePath = [mainBundle pathForResource:@"SDK" ofType:@"bundle"];
NSBundle *sdkBundle = [NSBundle bundleWithPath: sdkBundlePath];
NSString *filePath = [sdkBundle pathForResource:imageName ofType:nil];
UIImage *image = [UIImage imageWithContentsOfFile:filePath];
那么在 SDK工程 中,获取mainBundle,能使用这个吗NSBundle * mainBundle = [NSBundle bundleForClass:[self class]];?毕竟 self 是属于 SDK工程 中的类,这样用的话不就是跨领域了吗?
事实证明是可以的,可以用下图理解。
红色实线框,代表命名空间(独立空间)。
虚线框,代表归属于外部实线框。
对于静态库来说:
静态库在app中的关系.png
在iOS工程中,工程内部class是没有命名空间的,同一个工程,不能存在两个同样名字的class,就算用文件夹隔开也不行。因此,可以理解为无论是app工程的class还是SDK工程的class编译链接到同一个命名空间(.app/mainBundle),因此SDK工程中bundleForClass最终获取到的依旧是mainBundle。这也就是为什么,静态库开发中,静态库工程的class、全局变量都需要加上特殊前缀(或者重命名),以避免跟调用SDK的工程产生重复,导致编译错误。
而我们使用的bundle,是独立的命名空间。上图bundle嵌套于mainBundle中。因此读取bundle中的资源,也就需要通过
`mainBundle`的路径 + 自定义`bundle`名称 + 文件名
来读取了。
以上分析,是对于静态库而言。
动态库则是class和bundle都各自归属于独立的命名空间,因此通过bundleForClass来获取mainBundle就不适用了。
动态库在APP中的关系.png
通过创建一个动态库工程来测试,不清楚如何创建,可参考iOS SDK(一):静态库、动态库创建&接口测试
在动态库的SDK工程中,我们调用bundleForClass
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSLog(@"%@",bundle.bundlePath);
获取到的bundle地址的后段是:
.../Demo.app/Frameworks/SDK.framework
这是动态库跟静态库的区别了,动态库有自己独立的空间。我们通过解压集成动态库的ipa包,也可以直观的发现动态库***.framework就处于.../***.app/Frameworks/的路径下。Frameworks是用于存放动态库的文件夹。而对于静态库,解压ipa之后,是找不到的具体的.framework文件的。
而我们独立于***.framework的bundle,如果按照静态库的导入方式,则是存在于.../***.app/.bundle路径之下。
既然静态库和动态库,在使用bundleForClass存在如此的差异,使用的时候注意区分。
其实还有一种方式:
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *sdkBundlePath = [bundle pathForResource:@"SDK" ofType:@"bundle"];
NSBundle *sdkBundle = [NSBundle bundleWithPath:sdkBundlePath];
NSString *filePath = [sdkBundle pathForResource:@"test" ofType:@"png"];
就是无论静态库还是动态库,我们把自定义的bundle放在***.framework中,这样bundleForClass就通用了,工程导入SDK的时候,也不会漏掉bundle文件。
移入
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *sdkBundlePath = [bundle pathForResource:@"SDK" ofType:@"bundle"];
NSBundle *sdkBundle = [NSBundle bundleWithPath:sdkBundlePath];
NSString *filePath = [sdkBundle pathForResource:@"test" ofType:@"png"];
这样得到
- 动态库的资源路径是:
.../Demo.app/Frameworks/SDK.framework/SDK.bundle/test.png
- 静态库的资源路径是:
.../Demo.app/SDK.bundle/test.png
完美。
总结
-
静态库
class在宿主工程中没有独立的命名空间,编译后跟宿主工程是链接到一个位置的,假如SDK中使用了第三方库,则需要添加特殊的前缀(或者重命名),防止宿主工程使用了同样的第三方库导致编译出错。动态库则无需注意这个问题,可以放心大胆直接用第三方库。 -
动态库实测过可以上架的,只是
iOS10之后真机不能再从沙盒加载动态库了(模拟器还是可以的),也就不存在可以热更的可能。 -
SDK工程内部如何读取bundle,取决于bundle的位置。
1、对于静态库是固定的位置:.../***.app/***.bundle/,先获取.app路径.../***.app/,mainBundle和bundleForClass效果一致,再根据bundle名字获取路径下的***.bundle;
2、对于动态库,如果使用mainBundle,则是.../***.app/;
如果使用bundleForClass,是.../***.app/Frameworks/***.framework/。再根据bundle名字获取路径下的***.bundle。