库资源包括图片、xib、xib对应的图片、以及其它文件等。
本文只分析总结获取图片资源文件中所遇到的坑;大致原理其实都是一样,当你真正理解了其中的原理再获取其它资源是一样的容易。
我们制作的库有动态库、静态库之分;首先理解下:
Bundle
我们构建的app以及framework都是一个bundle,它又主要分为2种:
1)Bundle.main
The main bundle lets you access the resources in the same directory as the currently running executable. For a running app, the main bundle offers access to the app’s bundle directory. For code running in a framework, the main bundle offers access to the framework’s bundle directory.
可以理解为构建的.app就是一个main bundle
2)Bundle(for: AnyClass)
The NSBundle
object that dynamically loaded aClass
(a loadable bundle), the NSBundle
object for the framework in which aClass
is defined, or the main bundle object if aClass
was not dynamically loaded or is not defined in a framework.
通过在framework中定义的class返回一个bundle对象,如果没有定义或者动态加载那么返回的是main bundle,这里没有动态加载的意思就是我们平时制作的.a或者framework静态库返回的其实和main bundle是一样的。
本文调试开发的库是Swift版本,且framework静态库,并且这个库目前只有一个subspec子模块Extension(多个也是一样的),目录结构如下:
pod依赖可以在framework静态库以及源码进行切换,而上面已经说了如果是静态库,那么获取资源文件和宿主工程其实是一样的,怎么调试都能获取到资源文件,比较简单,如:
Bundle.main.loadNibNamed(<#T##name: String##String#>, owner: <#T##Any?#>, options: <#T##[AnyHashable : Any]?#>)
UIImage(named: <#T##String#>)
这种情况就不讨论了;
所以本文仅仅是调试验证在pod源码依赖(并且宿主app打包是通过动态库的形式,如果是静态库那么和上面一样比较简单就不讨论了)的情况进行开发调试。
为啥要研究这块?
因为你做好的库,并不确定对方拿到后在宿主工程中到底是以静态库的形式打包还是动态库的形式,所以为了通用性,必须都得实现。
1、Bundle(for: Self.self)
不能这样写,因为如果当前扩展子模块中是对UIImageView写的一个扩展,那么这里的self就表示的是UIImageView,而UIImageView是系统动态库里面定义的,所以得到的是:
系统的UIKitCore动态库。
2、查看Example.app里面的内容
选择target为Example,进行编译:
查看包里面的内容:
这就是扩展里面的资源bundle,查看里面的资源文件发现实际是放了2层,即framework--->Extension.bundle---->Extension.bundle/xxx.png,为什么呢?
是因为podspec挂载资源文件区别导致的
sp.resource_bundles = {
'Extension' => ['xxx/Assets/xxx.bundle']
}
sp.resource = 'xxx/Assets/xxx.bundle'
resource_bundles如果podfile控制不管是源码依赖还是库依赖打包成静态库或者动态库,那么生成的bundle资源文件都会被再包裹一层;xxx.bundle/xxx.bundle/.png
resource资源文件不会被包裹,只有一层。
这2种我们应该用哪一个呢?CocoaPods官方是强烈推荐用第一种,为了避免和main bundle中的资源文件名字冲突:
For building the Pod as a static library, we strongly recommend library developers to adopt resource bundles as there can be name collisions using the resources attribute.
现在很多第三方库带资源文件的不少使用的是第二种,其实如果按照规范,对应的bundle前面加上自己的库名字,这种冲突几率是很小的。
3、定义一个Class
为什么还要单独定义一个类呢?我们这里有1个特殊性,就是我们这里的子模块是一个Extension扩展,都是Swift系统库中Class的扩展,所以为了获取到当前bundle必须自定义一个class,我们这里创建一个xxExtension.swift,然后在Example中进行调试:
(lldb) po Bundle(for: xxExtension.self)
NSBundle </Users/xx/Library/Developer/CoreSimulator/Devices/700BD15-4005-4C6B-943B-73CA56C020F/data/Containers/Bundle/Application/996818F0-9F7A-42DC-A02D-066A32B5E940/xxBaseModule_Example.app> (loaded)
(lldb) po Bundle.main
NSBundle </Users/xx/Library/Developer/CoreSimulator/Devices/700BD15-4005-4C6B-943B-73CA56C020F/data/Containers/Bundle/Application/996818F0-9F7A-42DC-A02D-066A32B5E940/xxBaseModule_Example.app> (loaded)
(lldb)
????
为啥会是一样的?都是main bundle?官方文档不是说Bundle(for: AnyClass)中如果传入的是库中定义的类,应该返回的是xxBaseModule.framework才对,为啥不对???
原因:
还记得我们最上面的工程目录结构吗,project中有2个target,一个是我们的Example,一个是子Extension静态库的源码,而pod中是Development Pods本地私有库。
问题就出在Extension对应的target那里,我们当初创建它的目的是可以很方便的给这个组件库调试修改bug、增加功能等;虽然podfile中有引入这个组件,但是实际上我们Example中import的是import Extension 子模块,并不是基础模块xxBaseModule,而这个Extension对应的building我们当初定义的是一个静态库,所以无论你podfile如何更改,这里获取到的肯定是main bundle,那么我们要模拟pod源码依赖且动态库打包到宿主工程,我们应该如何做???
修改Extension对应的building为动态库。
4、Extension.framework和xxBaseModule.framework
编译后再次查看.app里面的内容,你会看到:
为啥获取到的是Extension.framework???
应该是xxBaseModule.framework才对啊,并且看上面截图也知道资源文件是放在这个里面的。
原因:
其实这里也还是因为上面Example项目中Extension对应的target导致的,这个时候就得新创建一个项目,通过podfile导入进行测试,正确。
其实这里是不用这么麻烦再创建一个新项目来验证的,目的是为了让自己能更清晰的理解bundle以及这里Example项目产生bug的原因。