CocoaPods的资源管理和Asset Catalog优化

本文转载自:https://juejin.im/post/5dd6665f6fb9a05a353ea2f8

针对目前公司业务子库资源引用方式各异,包体大小超出正常值的问题,作出了一些调研.同时网上的部分说法可能随着时间的推移和cocoapods的更新已经过时或不可用.本文展示了资源引用的各种方式,也防止大家走弯路.

这篇文章介绍了关于CocoaPods的资源管理行为,对于Pod库作者是必须了解的知识。同时介绍了CocoaPods使用Asset Catalog的注意事项。如果已经了解某方面知识,可以大致略过直接看结论。

Asset Catalog和App Thinning

Asset Catalog,是Xcode提供的一项图片资源管理方式。每个Asset表示一个图片资源,但是可以对应一个或者多个实际PNG图,比如可以提供 @1x , @2x , @3x 多张尺寸的图以适配;,还可以通过指定日间和夜间不同Appearances的两套图片。

这种资源,在编译时会被压缩,然后在App运行时,可以通过API动态根据设备scale factor来选择对应的真实的图片渲染。

App Thinning,是苹果平台(iOS/tvOS/watchOS)上的一个用于优化App包下载资源大小的方案。在App包提交上传到App Store后,苹果后台服务器,会对不同的设备,根据设备的scale factor,重新把App包进行精简,这样不同设备从App Store下载需要的容量不同,3x设备不需要同时下载1x和2x的图。

但是,这套机制直接基于Asset Catalog,换言之,只有在Asset Catalog中引入的图片,才可以利用这套App Thinning。直接拷贝到App Bundle中的散落图片,所有设备还是都会全部下载。因此如何尽量提升Asset Catalog利用率,是一个很大的包大小优化点。

CocoaPods的资源管理

CocoaPods是一个构建工具,它完全基于Pods的spec文件规则,在Podfile引入后,生成对应构建Xcode Target。也就是它是一个声明式构建工具(区别于Makefile这种过程式的构建工具)。对于资源的管理,目前有两个方式进行声明并引入,即 resourcesresource_bundles ,参考podspec syntax

虽然Podspec中包含所有待构建库的声明,但于CocoaPods也会根据Podfile的配置,动态调整最终的Xcode工程的配置,根据是否开启 use_framework! ,以下的资源声明最终的行为有所不同,这里分开介绍。

为了测试使用场景,我们新建了三个工程  工程目录如下:

主工程为 CatalogTest ,

资源工程为: ResourcesTest,ResourcesBubdlesTest

ResourcesTest 里包含资源文件:

ResourcesBubdlesTest包含资源文件:

不使用use_framework!

第一个测试:使用通配符导入 /Assets/**/*'

使用 resources

  • 导入方式 s.resources = 'ResourcesTest/Assets/**/*'

使用resource_bundles

s.resource_bundles = {

'ResourcesBubdlesTest' => ['ResourcesBubdlesTest/Assets/**/*']

}


app 目录结构

而通过resources 导入的文件直接会在根目录导入两份并且不会 经过Xcode的资源优化

bundle也会以bundle和里面的所有文件在根目录导入

通过resource_bundles 形式导入的文件会生成一个自命名的 budle ,bundle里包含文件

结论: 按照 **/* 导入的资源文件无论哪种形式都存在被重复导入的问题,这种懒省事的方式应该被制止

第二个测试:使用通配符导入  /Assets/*.xcasset' , /Assets/*.bundle

ResourcesTest:

xcasseets并入主工程 .car

Bundle 并入主目录

s.resources = ['ResourcesTest/Assets/*.xcassets',

'ResourcesTest/Assets/*.bundle'

]


ResourcesBubdlesTest:

注意 bundle必须有自己的命名空间,否则还是以文件形式导入

Bundle 文件会以命名空间为名字放在主目录下

s.resource_bundles = {

'ResourcesBubdlesTest' => ['ResourcesBubdlesTest/Assets/*.xcassets'],

'BlankLoading1' => ['ResourcesBubdlesTest/Assets/BlankLoading.bundle']

}


使用use_framework!

为了测试使用场景,我们新建了三个工程 工程目录如下:

主工程为 CatalogTest ,

资源工程为: ResourcesTest,ResourcesBubdlesTest

ResourcesTest 里包含资源文件:

ResourcesBubdlesTest包含资源文件:

第一个测试:使用通配符导入 /Assets/**/*'

使用 resources

  • 导入方式 s.resources = 'ResourcesTest/Assets/**/*'

    xcassets 生成.car, bundle文件依然双份

使用resource_bundles

  • 导入方式

s.resource_bundles = {

'ResourcesBubdlesTest' => ['ResourcesBubdlesTest/Assets/**/*']

}


app 目录结构

第二个测试:使用通配符导入  /Assets/*.xcasset' , /Assets/*.bundle

每个子工程有自己的 framework,也就是有自己的命名空间,不会出现命名冲突等问题

ResourcesTest:

xcasseets并入主工程 .car

Bundle 并入主目录

s.resources = ['ResourcesTest/Assets/*.xcassets',


'ResourcesTest/Assets/*.bundle'


]


ResourcesBubdlesTest:

s.resource_bundles = {

'ResourcesBubdlesTest' => ['ResourcesBubdlesTest/Assets/*.xcassets'],

'BlankLoading1' => ['ResourcesBubdlesTest/Assets/BlankLoading.bundle']

}


结论

无论任何场景,禁止 使用  podspecName/assers/**/* 形式引入资源文件,存在严重的资源重复引入问题,会显著增加包体大小!无法享有任何 Xcode的优化.

使用 resources

s.resources = ['ResourcesTest/Assets/*.xcassets',

'ResourcesTest/Assets/*.bundle'

]

使用 resource_bundles

s.resource_bundles = {

'ResourcesBubdlesTest' => ['ResourcesBubdlesTest/Assets/*.xcassets'],

'BlankLoading1' => ['ResourcesBubdlesTest/Assets/BlankLoading.bundle']

}


不使用use_framework!

当不使用use_framework!时,最终对Pod库,会创建单独的静态链接库 .a 的Target,然后CocoaPods会对主工程App Target增加自己写的脚本来帮助我们拷贝Pod的资源。

resources*.xcassets 资源会拷贝进入主目录 Assets.car , *.bundle 文件放入主目录下!

优点:

最简单暴力,而且由于固定了资源的路径在根路径上,如果先前在主工程目录中使用的代码,不需要更改一行即可继续使用。

缺点:

  1. 命名冲突问题,存在同名文件,会进行暴力合并!导致一个被替换掉.。因此,这要求所有资源文件命名本身,加入特定的前缀以避免冲突。类似的不止是图片,所有资源如 bundle , js , css 都可能存在这个问题,难以排查。而且由于这种拷贝到根路径的机制,这个问题不可从根源避免。

resource_bundles 文件 *.xcassets 会放入 命名空间.bundle 下的 Assets.car , *.bundle 放入主目录下的 命名空间.bundle

优点:

解决了部分命名冲突问题, *.xcassets 存在自己的命名空间.

缺点:

  1. bundle文件依然可能命名冲突

  2. 由于最终的资源文件增减了一级父文件夹.所以资源引用方式要做出改变

使用use_framework!

当使用了use_framework!之后,CocoaPods会对每个Pod单独建立一个动态链接库的Target,每个Pod最后会直接以Framework集成到App中。而资源方面,由于Framework本身就能承载资源,所有的资源都会被拷贝到Framework文件夹中而不再使用单独的脚本处理。

优点:

  1. 不会存在命名冲突。因为在使用use_framework!的情况下,由于资源本身被拷贝到Framework中,已经能最大程度减少冲突,因此这时候一般不需要考虑名称冲突问题.

  2. 都会使用Xcode的优化

缺点: 由于最终的资源文件增减了一级父文件夹.所以资源引用方式要做出改变

采用方案:

结合我们公司目前现状不能使用动态库.所以使用如下方案:

  1. 业务层使用 resources 导入资源

    • 资源文件使用 *.xcassets *.bundle *.text 方式导入,但是各子库文件会存在命名冲突问题,好处是不用改变资源引用方式,

    • 同时约定,各子库的资源添加子库名字缩写的前缀,避免暴力拷贝问题

  2. 基础服务层,和工具层使用 resource_bundles

    • 防止出现基础服务里的资源被暴力替换

    • 注意 .plist .json .html 需要建一个 bundleName.bunlde ,否则无法导入!

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章