CocoaPods的资源管理以及优化

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组件的目录结构为:
pod组件的目录结构
项目打包之后,ipa的的文件目录结构为:
ipa的的文件目录结构
经过查看.car文件与ipa文件看出使用s.resources声明的图片资源在打包之后文件时存放在ipa的包的一级目录下面的。

  1. 如果图片是放在x.xcassets文件夹下面的,会和项目主目录里面的x.xcassets合并共同生成一个.car目录,如果有同名文件,文件会覆盖。
  2. 如果图片没有放在x.xcassets文件下面只是放在pod里面的某个文件夹下面,那么图片会放在ipa的一级目录下面,由于主工程里面未放入x.xcassets文件里面的图片也会放在ipa的一级目录下面,所以也会出现同名文件覆盖问题。
使用s.resource_bundles管理资源
   s.resource_bundles = {
     'DLImageTest' => ['DLImageTest/Assets/*.xcassets','DLImageTest/Assets/*.png']
   }

pod组件的目录结构与上面一致不变,项目在打包之后,ipa的文件目录结构为:
ipa目录接口
DLImageTest.bundle的目录结构为:
在这里插入图片描述
经过查看.car文件与ipa文件看出使用s.resource_bundles声明的图片资源在打包之后文件时存放在ipa的包的一级目录下面的DLImageTest.bundle下面的。

  1. 使用x.xcassets文件来存放的图片会打包到DLImageTest.bundle文件下级目录的.car文件里面
  2. 如果图片没有放在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文件)
Frameworks文件目录
会为每个pod都生成一个Frameworks文件。子Frameworks的目录结构为
子Frameworks的目录结构
通过对比ipa文件以及查看.car文件可以看出:

  1. 如果图片是放在x.xcassets文件夹下面的,资源会放在每个pod组件的Frameworks文件下面的.car文件中。
  2. 如果图片没有放在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文件里面
在这里插入图片描述
在这里插入图片描述

  1. 使用x.xcassets文件来存放的图片会打包到子Frameworks文件下面的X.bundle文件的.car文件里面
  2. 如果图片没有放在x.xcassets文件下面只是放在pod里面的某个文件夹下面,那么图片会直接拷贝到子Frameworks文件下面的x.bundle文件下级目录里面。

结论

Podfile不使用use_framework

  1. 使用s.resource来管理资源,pod中x.xcassets的资源图片会与主工程中x.xcassets的资源图片合并共同生产一个.car文件,pod未放在x.xcassets里面的图片与资源会直接拷贝到ipa文件的一级目录里面。
  2. 使用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文件里面。所以不会出现文件覆盖问题.

  1. 使用s.resource来管理资源,pod中x.xcassets的资源图片会在子framework中生产一个.car文件,pod未放在x.xcassets里面的图片与资源会直接拷贝到子framework中。
  2. 使用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;
}

方法二与方法三的使用场景是:

  1. 不想受主工程资源文件的影响时候使用
  2. 如果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文件中。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值