CocoaPods的资源管理和Asset Catalog优化
Asset Catalog和App Thinning
Asset Catalog
Asset Catalog是Xcode提供的一项图片资源管理方式。每个Asset表示一个图片资源,但是可以对应一个或者多个实际PNG图,比如可以提供@1x,@2x,@3x多张尺寸的图以适配;还可以通过指定日间和夜间不同Appearances的两套图片,这种资源,在编译时会被压缩,然后在App运行时,可以通过API动态根据设备scale factor来选择对应的真实的图片渲染
App Thinning
App Thinning 是一个关于节省iOS设备存储空间的功能,它可以让AppStore和操作系统在安装、更新及运行iOS或者 watchOS的App等场景时,会对不同的设备,根据设备的scale factor,Apple 服务会自动将安装包切割为不同的应用变体(App variant),或者称 App Store会针对不同的设备制作不同的"简化版App",当你下载app时,系统会根据设备型号下载安装不同的"简化版 app"
但是,这套机制直接基于Asset Catalog,换言之,只有在Asset Catalog中引入的图片,才可以利用这套App Thinning。直接拷贝到App Bundle中的散落图片,所有设备还是都会全部下载。
CocoaPods的资源管理(如果不想看分析过程可以直接跳过看结论)
Podfile不使用use_framework
使用s.resources管理资源
在pod文件中使用s.resources来管理资源
s.resources = ['DLImageTest/Assets/*.xcassets','DLImageTest/Assets/*.png']
pod组件的目录结构为:
项目打包之后,ipa的的文件目录结构为:
经过查看.car文件与ipa文件看出使用s.resources声明的图片资源在打包之后文件时存放在ipa的包的一级目录下面的。
- 如果图片是放在x.xcassets文件夹下面的,会和项目主目录里面的x.xcassets合并共同生成一个.car目录,如果有同名文件,文件会覆盖。
- 如果图片没有放在x.xcassets文件下面只是放在pod里面的某个文件夹下面,那么图片会放在ipa的一级目录下面,由于主工程里面未放入x.xcassets文件里面的图片也会放在ipa的一级目录下面,所以也会出现同名文件覆盖问题。
使用s.resource_bundles管理资源
s.resource_bundles = {
'DLImageTest' => ['DLImageTest/Assets/*.xcassets','DLImageTest/Assets/*.png']
}
pod组件的目录结构与上面一致不变,项目在打包之后,ipa的文件目录结构为:
DLImageTest.bundle的目录结构为:
经过查看.car文件与ipa文件看出使用s.resource_bundles声明的图片资源在打包之后文件时存放在ipa的包的一级目录下面的DLImageTest.bundle下面的。
- 使用x.xcassets文件来存放的图片会打包到DLImageTest.bundle文件下级目录的.car文件里面
- 如果图片没有放在x.xcassets文件下面只是放在pod里面的某个文件夹下面,那么图片会直接拷贝到DLImageTest.bundle文件下级目录里面
Podfile使用use_framework
当使用了use_framework!之后,CocoaPods会对每个Pod单独建立一个动态链接库的Target,每个Pod最后会直接以Framework集成到App中。而资源方面,由于Framework本身就能承载资源,所有的资源都会被拷贝到Framework文件夹中而不再使用单独的脚本处理
使用s.resource管理资源
s.resources = ['DLImageTest/Assets/*.xcassets','DLImageTest/Assets/*.png']
通过打包查看以及分析ipa的文件目录结构可以看出:
使用use_framework之后ipa文件目录里面多了一个Frameworks的文件夹,里面放了多个子Frameworks文件,里面生成了多个子Frameworks文件(每个pod都会生成一个X.Frameworks文件)
会为每个pod都生成一个Frameworks文件。子Frameworks的目录结构为
通过对比ipa文件以及查看.car文件可以看出:
- 如果图片是放在x.xcassets文件夹下面的,资源会放在每个pod组件的Frameworks文件下面的.car文件中。
- 如果图片没有放在x.xcassets文件下面只是放在pod里面的某个文件夹下面,图片会直接拷贝到每个pod组件的Frameworks的下级文件里面。
使用s.resource_bundles管理资源
s.resource_bundles = {
'DLImageTest' => ['DLImageTest/Assets/*.xcassets','DLImageTest/Assets/*.png']
}
通过打包查看以及分析ipa的文件目录结构可以看出:与使用s.resource相同use_framework之后ipa文件目录里面多了一个Frameworks的文件夹,里面放了多个子Frameworks文件,里面生成了多个子Frameworks文件(每个pod都会生成一个X.Frameworks文件),与使用s.resource不同的是使用了s.resource_bundles之后,每个子Frameworks文件里面多了一个bundles文件,资源文件都放在了bundles文件里面
- 使用x.xcassets文件来存放的图片会打包到子Frameworks文件下面的X.bundle文件的.car文件里面
- 如果图片没有放在x.xcassets文件下面只是放在pod里面的某个文件夹下面,那么图片会直接拷贝到子Frameworks文件下面的x.bundle文件下级目录里面。
结论
Podfile不使用use_framework
- 使用s.resource来管理资源,pod中x.xcassets的资源图片会与主工程中x.xcassets的资源图片合并共同生产一个.car文件,pod未放在x.xcassets里面的图片与资源会直接拷贝到ipa文件的一级目录里面。
- 使用s.resource_bundles来管理资源,pod中的资源文件会放到自己通过s.resource_bundles中命名的一个或者多个bundles文件里面,其中使用x.xcassets的资源图片会放到对应的bundles文件下面的.car文件里面,未放在x.xcassets里面的图片与资源会直接拷贝到对应的bundles文件里面。
使用s.resource简单暴力,在项目的任何地方都可以很方便的引用图片以及文件资源。但是主项目与pod里面的重名资源会产生覆盖。
使用s.resource_bundles不会出现文件覆盖,但是如果pod组件过多,每个pod都会生成一个到多个bundles文件而且如果使用.xcassets方式存放图片,bundles里面会生成.car文件,导致包体积增加。
Podfile使用use_framework
项目打包之后生成的ipa文件比不使用use_framework打包的ipa文件相比多了一个framework文件夹,苹果会为每个pod都生成一个子framework文件(文件的名称是以pod工程名称命名的),然后都放入ipa文件下面的framework文件里面。所以不会出现文件覆盖问题.
- 使用s.resource来管理资源,pod中x.xcassets的资源图片会在子framework中生产一个.car文件,pod未放在x.xcassets里面的图片与资源会直接拷贝到子framework中。
- 使用s.resource_bundles来管理资源,pod中的资源文件会放到子framework中自己通过s.resource_bundles中命名的一个或者多个bundles文件里面,其中使用x.xcassets的资源图片会放到子framework中对应的bundles文件下面的.car文件里面,未放在x.xcassets里面的图片与资源会直接拷贝到子framework中对应的bundles文件里面
使用(以图片的使用为例)
方法一
image.image = [UIImage imageNamed:@""];
不管是哪种方案,都可以使用该方法来获取图片,不过如果使用的图片名称存在多张图片,那么展示会出现问题。
方法二
/// 在use_frameworks模式下使用s.resources声明资源时使用
/// @param imageName 图片名称
- (UIImage *)getResourceImageName:(NSString *)imageName {
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
// 获取Bundle中的UIImage
UIImage *image = [UIImage imageNamed:imageName inBundle:bundle compatibleWithTraitCollection:nil];
return image;
}
方法三
/// 在使用s.resource_bundles声明资源时使用
/// @param imageName 图片名称
/// @param bundleName 使用s.resource_bundles时候设置的bundle名称
- (UIImage *)getResourceBundleImageName:(NSString *)imageName bundleName:(NSString *)bundleName{
bundleName = [NSString stringWithFormat:@"/%@.bundle",bundleName];
NSString *bundlePath = [[NSBundle bundleForClass:[self class]].resourcePath
stringByAppendingPathComponent:bundleName];
NSBundle *resource_bundle = [NSBundle bundleWithPath:bundlePath];
UIImage *image = [UIImage imageNamed:imageName
inBundle:resource_bundle
compatibleWithTraitCollection:nil];
return image;
}
方法二与方法三的使用场景是:
- 不想受主工程资源文件的影响时候使用
- 如果pod文件是功能性组件,例如百度的人脸识别sdk、一些ocr识别工具等。可以集成到任何地方,为了防止资源冲突,所以可以使用该方案
方法四
/// 获取文件所在name,默认情况下podName和bundlename相同,传一个即可
/// @param bundleName bundle名字,就是在resource_bundles里面的名字
/// @param podName podName pod的名字
/// @return bundle 返回的bundle文件
+ (NSBundle *)bundleWithBundleName:(NSString *)bundleName podName:(NSString *)podName{
if (bundleName == nil && podName == nil) {
@throw @"bundleName和podName不能同时为空";
}else if (bundleName == nil ) {
bundleName = podName;
}else if (podName == nil) {
podName = bundleName;
}
if ([bundleName containsString:@".bundle"]) {
bundleName = [bundleName componentsSeparatedByString:@".bundle"].firstObject;
}
//没使用framwork的情况下
NSURL *associateBundleURL = [[NSBundle mainBundle] URLForResource:bundleName withExtension:@"bundle"];
//使用framework形式
if (!associateBundleURL) {
associateBundleURL = [[NSBundle mainBundle] URLForResource:@"Frameworks" withExtension:nil];
associateBundleURL = [associateBundleURL URLByAppendingPathComponent:podName];
associateBundleURL = [associateBundleURL URLByAppendingPathExtension:@"framework"];
NSBundle *associateBunle = [NSBundle bundleWithURL:associateBundleURL];
associateBundleURL = [associateBunle URLForResource:bundleName withExtension:@"bundle"];
}
NSAssert(associateBundleURL, @"取不到关联bundle");
//生产环境直接返回空
return associateBundleURL?[NSBundle bundleWithURL:associateBundleURL]:nil;
}
如果把获取资源的方法封成一个公共的方法,例如放在公共的pod组件中,其他pod通过调用这个pod来获取资源的时候,[self class]就不正确了,这时可以通过调用方法四来获取正确的资源。
最后
个人想法:如果项目是组件化开发,不论是否使用use_framework,建议使用s.resource来管理图片。而且图片存放到x.xcassets文件中(超过150k的大图除外)。
ps:因为.xcassets的机制为缓存机制,当图片在第一次被调用的时候,.xcassets文件中的图片就会存入缓存中(这样图片的二次调用会很快)。所以大图可以不放入.xcassets文件中。