iOS组件化初探( 五 )


?resource_bundles
This attribute allows to define the name and the file of the resource bundles which should be built for the Pod. They are specified as a hash where the keys represent the name of the bundles and the values the file patterns that they should include.
这俩个属性选择其中一个使用即可 , 二者最主要的区别在于 resource_bundles 会创建独立的 bundle , 能够有效解决资源文件重名问题 , 也是 CocoaPods 强烈建议使用的属性 。resource 属性则不会创建属于 Pod 库自己的 bundle , 打包时资源文件会被拷贝到 main bundle 里 。
这里我们使用resource_bundles属性 , =>符号左侧表示 bundle 的名字 , 建议 bundle 名字中至少要包含 Pod 库的名字以最大限度的防止命名冲突 。=>符号右侧是资源文件的路径 。
首先在 QRCodeReader 根目录下创建Resources文件夹 , 用于存放资源文件 , 并在其中创建一个Assets.xcassets文件夹用于管理图标资源 。之后在主工程运行pod install , 打开工程后可以看见在QRCodeReader目录下面多了一个Assets , 把组件用到的图标从主工程中移除并添加到组件库的Assets中 。
这时编译运行起来后会发现这些图标没有加载成功 , 原因在于我们使用的UIImage(named: String)方法只会在main bundle中查找图片资源 , 而我们之前的操作把图标资源放到了一个单独的 bundle 中 , 因此在加载图片时需要指定图片所在的 bundle 。
因为在Podfile中使用了use_frameworks! , 组件最终会以 framework 的形式集成到 App 包中 , 而 bundle 文件位于 App 的"
/Frameworks/QRCodeReader.framework/QRCodeReader.bundle"目录下 , 我们可以通过如下代码来加载指定 bundle 中的图标资源:
// App main bundle 根路径let mainPath = Bundle.main.resourcePath // QRCodeReader.bundle的相对路径let pathComponent = "/Frameworks/QRCodeReader.framework/QRCodeReader.bundle"// 获取bundle对象let bundle = Bundle(path: mainPath + pathComponent)// 获取图片资源let image = UIImage(named: imageName, in: bundle, compatibleWith: nil)稍作封装:
func image(named: String, in bundleName: String) -> UIImage? {let mainPath = Bundle.main.resourcePathlet pathComponent = "/Frameworks/(bundleName).framework/(bundleName).bundle"let bundle = Bundle(path: mainPath + pathComponent)if let image = UIImage(named: named, in: bundle, compatibleWith: nil) {return image} else {return UIImage(named: named) // 兜底策略}}如果没有使用use_frameworks! , 而是采用静态库的形式集成 ,  bundle 文件会位于 App main bundle 的根目录下 。此时上述代码的pathComponent应该修改为:
let pathComponent = "/(bundleName).bundle"其余部分不变 。
为了能够兼容使用静态库和使用动态库两种情况 , 我们把两种情况下加载图片的代码合并处理:
public func image(named: String, in bundleName: String) -> UIImage? {if let image = _dynamicImage(named: named, in: bundleName) {return image} else if let image = _staticImage(named: named, in: bundleName) {return image} else {return UIImage(named: named)}}private func _staticImage(named: String, in bundleName: String) -> UIImage? {let pathComponent = "/(bundleName).bundle"return _image(named: named, with: pathComponent)}private func _dynamicImage(named: String, in bundleName: String) -> UIImage? {let pathComponent = "/Frameworks/(bundleName).framework/(bundleName).bundle"return _image(named: named, with: pathComponent)}private func _image(named: String, with pathComponent: String) -> UIImage? {guard let mainPath = Bundle.main.resourcePath else { return nil }let path = mainPath + pathComponentlet bundle = Bundle(path: path)return UIImage(named: named, in: bundle, compatibleWith: nil)}对于其他类型资源文件的加载 , 大家可参考图片加载方式自行实现 , 这里就不一一介绍了 。
到此为止 , 一个最基本的组件化改造实践就完成了 , 由于案例中的QRCodeReader作为演示 , 本身体量较小且和外部耦合性不强 , 整个改造过程比较顺利 。实际项目中尤其是对业务进行组件化改造时 , 往往需要处理复杂的依赖关系 , 以及管理类和接口的访问级别 。
6.子组件前文已经提到了 , QRCodeReader本身体量很小 , 实际开发中不足以单独成立一个组件 。但是又存在将其独立出来的必要性 , 这种情况在项目中并不少见 。这时 , 我们可以将它们配置成sub-specifications(暂且称其为子组件) , 并放到同一个组件库中 。


推荐阅读