在组件化前期的工作中,我们会面临如何管理图片、音视频等资源的问题。我们需要关注的问题是如何将相应的资源和组件一起打包,并保证能够在组件内和组件间的正常使用。以下内容均针对于使用CocoaPods方式组件化对资源文件管理的讨论。
文件管理方式:
1. 集中式管理
将所有的资源文件做成一个组件,其它相应的组件依赖该资源组件,通过组件暴露的相关接口获取对应的资源。
2. 分散式管理
将资源文件进行清晰分类,对应的资源文件嵌入至对应的组件中,如有共用文件,抽取公共资源组件将公共资源放入该组件中,或者将公共资源直接放至主工程中。
集中式管理与分散式管理优缺点对比:

总结建议:
集中式管理可方便维护,可对项目文件进行统一管理,此外还可降低文件冗余的概率,可用于较大、耦合严重的项目。倘若是新项目或者组件对主工程依赖性不强的组件,可采用分散式管理,将组件的相关资源与组件绑定。
resources和resource_bundles
resoures和resource_bundles是CocoaPods两种资源文件引用的方式。
1. resource/resources
resource与resources两个属性功能相同,不同的是resources可以批量指定文件资源,resource只能指定单个文件资源。
1.1 语法
spec.resource = 'Resources/HockeySDK.bundle'
spec.resources = ['Images/*.png', 'Sounds/*']
1.2 官方描述 Podspec语法官方介绍
resources将指定的资源复制到目标bundle,我们强烈建议开发者使用 resource bundles去构建静态资源库。使用resources属性仅仅是将指定的文件资源复制到目标bundle,如此Xcode不会对相关资源进行优化操作。
看完官方描述,我们第一直觉就会放弃使用这种方式了。虽然如此,但是我们还是去看看如果使用这种方式具体会产生哪些影响。
1.3 resource探究
使用pod lib create SCResource_Resources命令创建项目。打开Example中的项目,并删除SCResource_Resources.podspec中无用的代码。如下图所示。

1.3.1 resource不嵌入xcassets文件
选中ReplaceMe.m文件,右键Show in Finder,调至上一级文件夹,看到Classes和Assets文件夹。我们把ReplaceMe.m删除,并删除SCResource_Resources.podspec中的s.source_files,因为我们在资源组件中暂时不用编辑代码。然后把事先准备好的图片资源放入Assets文件夹下,并设置resource属性。最终如下图所示。

终端pod install后,便可以看到图片资源已经被加到Resources文件夹下。

查看资源文件在包中的位置
真机运行,选择Products文件夹下的SCResource_Resources_Example.app右键Show in Finder,选中SCResource_Resources_Example右键,选择显示包内容,就可以看到我们添加的Images文件夹。查看并记录文件夹的大小。发现和事先我们准备的文件夹大小相同,均为21.5M。

资源文件获取
一般情况下,我们在项目中获取图片都是通过使用imageNamed:方法去获取。那么现在我们把图片资源放在组件中,通过这样的方式也能够获取吗?
我们在Example项目中的SCViewController.m的viewDidLoad方法中键入如下代码:

前面我们查看过
Images最终在APP中的路径,并且容易找到goodluck_smile图片的路径是Images/好运墙/goodluck_smile。运行项目,发现我们拿到的image对象是nil。
很糟糕,我们没有获取到对应的图片。查看注释可以知道
imageNamed:是从main bundle中获取文件资源,那么如果我们把图片放在主工程中的Images.xcassets文件中,这里的文件在最终的包中的路径是什么呢?带着这样的疑问,我们简单地把一张图片(goodluck_smile)放入Images.xcassets中,真机运行后,通过上述查看资源文件在包中的位置的操作方法进行查看。
可以看到多出了
Assets.car文件,由此可以知道,Images.xcassets中的图片资源,最终会被打包成Assets.car文件,也从侧面可以说明Assets.car文件所在的目录就是main bundle的路径,那么为什么组件中的图片没有被正常获取呢?难道是因为我们路径问题?前面我们已经说过
goodluck_smile图片是在Images/好运墙/ 下,那么我么手动拼接试试。为了排除其它影响,我们删除掉前面在Images.xcassets中的图片文件,并运行。

这时候,我们正确获取到了我们想要的图片。现在我们可以通过代码来获取到组件中的图片了,需要注意的点是需要传入图片的相对路径,那么在xib中又如何呢?
我们再Main.storyboard中添加一个UIImageView,并直接设置goodluck_smile图片,瞬间就心情大好,因为立马就看到Main.storyboard显示了对应的图片。

真机运行,看看会不会有什么问题。
运行后发现,我们设置的图片没有正常显示,那么也是因为我们要填写相对路径的原因吗?我们去试试。

这时候,发现Main.storyboard没有正常显示图片,但是真机运行后,图片显示正常。
总结:
使用resource/resources直接存放文件资源时,无论是通过代码获取图片,还是在xib中设置图片,都需要填写完整的相对路径。当然如果你想直接通过设置图片名称的方式获取图片,那么你必须将图片直接暴露在resources文件下,不能新建文件对相关资源做整理。
1.3.2 resource嵌入xcassets文件
在没有组件化时,我们一般把图片资源都放在Images.xcassets文件内管理,其为我们提供了许多优化点和一些方便的功能,所以我们可能也希望在组件中也利用这些优化和功能。那么在resource中如何通过xcassets来管理图片呢?其实很简单,只需在组件的Assets文件夹下创建Asset Catalog文件,再将图片资源拖入即可。

这里需要注意的是:在创建Asset Catalog文件后,其目录可能不在组件的Assets文件下,需要手动将其拖入至文件下。

pod install后,对项目进行编译,如果出现如下错误,则选择File -> Workspace Settings -> Build System的New Build System(Default)改为Legacy Build System即可。

使用上文提到的查看资源文件在包中的位置的方式查看文件资源,前面我们也提及到,xcassets文件最后打包进APP是会转成Assets.car文件的,我们找到该文件,并查看该文件的大小。

文件大小变成了69.1M,是原先21.5M的好几倍,这会大大增大包的大小。
资源文件获取
在查看资源文件路径后,我们发现,其路径和在主工程中的Images.xcassets在包中的路径相同,那么可以推测,正常使用相关方法应该可以获取到资源文件。
同样在ViewDidLoad方法中键入一下代码,看能否正确获取图片资源。断点运行后,发现可正常获取。

使用xib方式也一样,这里就不再截图,大家可以自己尝试。
总结:
resource嵌入xcassets文件时,资源文件会被copy至main bundle中,可以正常获取资源文件,但是会造成APP大小变大,因此不建议使用。
2. resource_bundle/resource_bundles
和resource/resources类似,resource_bundle/resource_bundles功能相同,区别在与指定一个和多个。
2.1 语法
spec.ios.resource_bundle = { 'MapBox' => 'MapView/Map/Resources/*.png' }
spec.resource_bundles = {
'MapBox' => ['MapView/Map/Resources/*.png'],
'MapBoxOtherResources' => ['MapView/Map/OtherResources/*.png']
}
2.2 官方描述 Podspec语法官方介绍
重点翻译:强烈建议使用该方式为Pod构建静态库,文件资源通过键值匹配资源,bundle的名称应该包含Pod的名称来降低名称冲突的可能性。
2.3 resource_bundle探究
下面同样通过是否嵌入xcassets文件来分析这两种情况的优劣。
2.3.1 resource_bundle不嵌入xcassets文件
不嵌入xcassets文件时,和resource一样,直接将文件资源拖入至Assets文件夹下,具体参考上文resource不嵌入xcasset文件中的内容。然后修改podspec文件制定文件资源的方式,如下图所示。

pod install,真机运行,查看资源文件在包中的位置,可以看到一个SCResource_Resources.bundle的文件。查看该文件的大小为21.1M,比原文件略小。

再选择SCResource_Resources.bundle右键显示包内容,可看到我们放进去的Images图片文件夹。

资源文件获取
前面在resource的章节中,我们已经知道需要通过拼接资源的相对路径才能获取相应的资源,所以我们这里也尝试看看会发生什么。
在ViewDidLoad方法中键入下面代码:
UIImage *image = [UIImage imageNamed:@"SCResource_Resources.bundle/Images/好运墙/goodluck_smile"];
断点查看是否能正常获取图片资源。
在xib中同样这样拼接,真机运行,看能否正常显示图片。

运行后,我们可以如预期一样获取资源文件。

总结:
resource_bundle不嵌入xcasset文件,需拼接文件的相对路径才能正确获取图片资源。
2.3.2 resource_bundle嵌入xcassets文件
嵌入xcassets文件时,也和resource一样,创建xcassets文件,拖入文件资源,并拖入至Assets文件夹下,具体参考上文resource嵌入xcasset文件中的内容。
pod install并真机运行,查看资源文件在包中的位置,我们同样可以看到SCResource_Resources.bundle文件,查看文件大小,可以看到只有16.8M,比前面所有情况都要小。

再选择SCResource_Resources.bundle右键显示包内容,可看到我们放进去的Assets.car文件。与前文的情况一致。
资源文件获取
在resource的章节中,如果嵌套xcassets文件,我们可以直接通过图片名称来获取文件资源,那么这里是不是类似呢,我们来试试。
在viewDidLoad方法中键入下面代码:
UIImage *image = [UIImage imageNamed:@"goodluck_smile"];
运行,查看image对象是否存在。

很遗憾,结果为nil。那么我们拼接路径呢?同样在viewDidLoad方法中键入下面代码:
UIImage *image = [UIImage imageNamed:@"SCResource_Resources.bundle/goodluck_smile"];
运行,查看image对象是否存在。

同样的结果,还是nil。
使用这种方式,我们需要换一个方法去获取指定资源,我们需要调用UIImage的imageNamed:inBundle: compatibleWithTraitCollection:方法。指定bundle和图片的名称即可。
NSString *bundleName = @"SCResource_Resources";
NSString *imageBundlePath = [[NSBundle mainBundle] pathForResource:bundleName ofType:@"bundle"];
NSBundle *imageBundle = [NSBundle bundleWithPath:imageBundlePath];
UIImage *image = [UIImage imageNamed:@"goodluck_smile" inBundle:imageBundle compatibleWithTraitCollection:nil];
运行,查看image对象是否存在

运行结果如预期,可获取对应文件资源。
在xib中如何设置呢?可以添加分类暴露bundleName和imageName使用IBInspectable修饰,调用imageNamed:inBundle: compatibleWithTraitCollection:方法。
总结:
resource_bundle嵌入xcasset文件,包文件大小相对于其它情况较小,但获取文件资源时,需要封装方法调用UIImage的imageNamed:inBundle: compatibleWithTraitCollection:方法。
总结
使用CocoaPods方式组件化,对文件资源进行管理,建议使用resource_bundle/resource_bundles嵌入xcassets文件的方式。这样一来可以使用xcassets的一些特性和优化,也能够在一定程度上减小包的体积。