在组件化前期的工作中,我们会面临如何管理图片、音视频等资源的问题。我们需要关注的问题是如何将相应的资源和组件一起打包,并保证能够在组件内和组件间的正常使用。以下内容均针对于使用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
的一些特性和优化,也能够在一定程度上减小包的体积。