iOS 包体积优化方案与实践(二)

707 阅读8分钟

11

一. 前言

iOS应用程序的包体积大小是衡量其性能的重要指标,因为过大的包体积不仅会降低用户的下载意愿,还会增加用户的下载等待时间和手机存储空间的占用。本文重点介绍资源优化,在洞窝APP实践中,资源优化包括大资源优化和无用代码文件等。不管是资源优化还是代码优化,都需要分析Mach-O文件,以获取资源和代码的引用关系,本文先详细介绍Mach-O文件。

二. Mach-O文件

丨2.1 简介

Mach-O为Mach Object文件格式的缩写,用于记录可执行文件、目标代码、动态库和内存转储的文件格式,是运用于Mac以及iOS系统上。

Mach-O文件的基本结构:
  • Header:头部,包含可以执行的CPU架构,比如x86,arm64。
  • Load commands:加载命令,也就是告诉系统如何去处理不同的加载信息。包含文件的组织架构和在虚拟内存中的布局方式。
  • Data:数据,包含Load commands中需要的各个段(segment)的数据,每个Segment的大小都是Page的整数倍。

image.png

丨2.2 数据段

Mach-O的Data部分,其实是真正存储APP二进制数据的地方,前面的header和load command,仅是提供文件的说明以及加载信息的功能。

Data(数据段): 主要是代码、数据,包含了Load commands中需要的各个段(Segment)的数据,每个Segment可以有多个Section,下面列举一些常见的 Section。在Data(数据段)中,大写的字符串(如__TEXT)代表的是Segment,小写的字符串(如__objc_methtype)代表的是Section。 image.png

丨2.3 分析Mach-O文件

可以用MachOView能查看MachO文件信息,如下图所示。

image.png

Mach-O文件获取:Xcode打包好的iPA,改后缀名为.zip,然后解压缩得到payload文件夹,其中有xxx.app,右键显示包内容,其中有xxx的exec文件,即是Mach-O文件。

Mach-o文件中__DATA __objc_classrefs段记录了引用类的地址,__DATA __objc_classlist段记录了所有类的地址,取差集可以得到未使用的类的地址,然后进行符号化,就可以得到未被引用的类信息。

也可以通过第三方工具检测无用类,我们这里用到了WBBlades

通过该工具可以清晰看到app包体积的大小分布。WBBlades也是基于Mach-O文件解析, 相比已公开的常见方案,支持对类的继承、动态调用、自身类调用、属性及成员变量等情景下的无用类检测,无用类识别精度更高。

三. 资源优化

丨3.1 On-Demand Resource

On-Demand Resource是托管在app Store上的应用程序内容,与你下载的相关应用程序包是分开的。它们可以实现更小的应用包、更快的下载和更丰富的应用内容。应用程序请求按需资源集,操作系统管理下载和存储。应用程序使用资源,然后释放请求。下载后,资源可能会通过多个启动周期留在设备上,从而使访问速度更快。

以下是按需加载支持的类型。这个系统集成的下载功能和我们常用下载功能是有区别的:

1.On-Demand Resources中的涉及的资源是在app打包时就确定了的,不进行版本更新就无法更新这些资源。

2.由于下载的Server是app store替我们实现的,我们就不需要服务器了。

以下是按需加载支持的类型

image.png

On-Demand Resource的三种标签:

  • Initial install tags.此种标签的资源,会随着App从App Store下载而下载,但是会影响App的ipa大小,也就是说此种资源会包含在ipa内。
  • Prefetch tag order.此种标签会在App下载后,开始下载相应的资源,下载是存在顺序的,后面会说明。此种资源并不会影响ipa的大小,也就是说此种资源并不包含在ipa内。
  • Dowloaded only on demand. 此种标签下的资源,会在必要的时候,主动触发下载,这是我们开发者自己控制下载时机的。

目前项目中大多用到的是第二种按需加载

苹果规定标签用于识别和管理一组按需资源。为项目中的资源分配一个或多个标签会将其识别为按需资源。在运行时,所有管理按需资源的调用都使用标签,而不是单个资源。

image.png 可以看到,使用按需加载节省了大约20多MB的包体积。

按需资源生命周期

image.png

  1. app请求tag。
  2. 本地存储资源文件。
  3. 托管在appstore上。
  4. 下载相关资源文件。
  5. 下载资源文件。
  6. 保存资源文件。
  7. app结束使用资源文件tag。
  8. 释放资源文件。
  9. 清除资源文件。

启用按需资源

图: 启用按需资源设置。

image.png

一个app可以设置多个tag,资源放在对应tag下,点击资源可以看到,如下图

image.png

进入APP请求资源

  +(NSBundleResourceRequest *)conditionallyBeginAccessingResources:(void (^)(BOOL success))successBlock showLoading:(BOOL)show

{
    NSSet *set =[NSSet setWithObjects:@"assetsPdf", @"assetsIcon",@"assetsOther",nil];
    // 根据set初始化NSBundleResourceRequest对象
    NSBundleResourceRequest *tagRequest = [[NSBundleResourceRequest alloc] initWithTags:set];
    // 判断请求的资源是否存在当前设备上
    [tagRequest conditionallyBeginAccessingResourcesWithCompletionHandler:^(BOOL resourcesAvailable) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (resourcesAvailable)
            {
                // 获取加载资源的路径
                NSLog(@"设备存在该资源");
                if(successBlock)
                {
                    successBlock(YES);
                }
            }
            else {
                NSLog(@"设备不存在该资源");
                [tagRequest beginAccessingResourcesWithCompletionHandler:^(NSError * _Nullable error)
                 {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        if (error) {
                            NSLog(@"加载资源出现错误%@", error);
                            if(show)
                            {
                                NSLog(@"资源加载失败,请重试");
                            }
                            if(successBlock)
                            {
                                successBlock(NO);
                            }
                        }
                        else {
                            NSLog(@"加载资资源成功");
                            if(successBlock)
                            {
                                successBlock(YES);
                            }
                        }
                    });
                }];
            }
        });
    }];
    return tagRequest;

}

(注意,有时候Debug环境图片资源加载不出来。TestFligth和线上无问题)

异常处理

  • NSBundleOnDemandResourceOutOfSpaceError 错误在用户设备空间不足,无法下载请求的资源时发生。需要用户清理空间,然后重试。
  • NSBundleOnDemandResourceExceededMaximumSizeError 在资源超过 app 按需加载资源的最大内存限制时,会出现这个错误。需要清理资源。
  • NSBundleOnDemandResourceInvalidTagError 请求的资源tag找不到。需要去确认一下正确的tag字段。

丨3.2 远程资源服务器

将本地图片资源放到远程服务器,按需下载,优点是可以根据业务需要按需加载,利用sdwebImage等第三方的图片处理方案做几级缓存,这样既节省了流量资源也节省了内存资源(本质就是加载网络图片的那套逻辑)。缺点就是需要维护网络资源,以及做版本管理,远程冗余的资源要定时清除,还有就是第一次进入APP没有网络图片缓存的时候,页面需要一定时间才能展现出来。

这部分内容我们将图片按大小以及模块做了个分类,10k以上的资源全部迁移到远程,以下的仍然本地存储,这部分优化掉接近20M的空间。

后续需要控制10k以上的资源放入APP内,控制非必要的图片资源放到远程服务器上,防止APP包体积再次增大。

丨3.3 资源压缩

资源压缩很好理解,顾名思义就是对资源进行各种方式的压缩,在洞窝APP中最主要的资源就是图片,其他类型占比很小。

针对图片有损压缩可以利用tinypng进行有损压缩压缩工具来压缩图片,对于大图,可以压缩到60%,在保证不失真的情况下可以进行多次压缩,无论是远程的图片资源还是本地图片资源,都进行压缩之后,可以节省图片下载时候和app的包体积。需要注意的是,因为是有损压缩,压缩次数过多,图片会稍微模糊,需要自行判断压缩到可以接受的程度,在图片大小和质量中选择平衡点。

另外图片资源可以考虑使用webp格式,对于压缩之后留下来的小于10k的本地图片也需要注意以下两点:

  • 使用Assets来管理资源,而不是传统的直接将图片加入工程的方式
    这样会有两个好处:1)相比传统方式,加载速度快 2)下载后的安装包里,只会包含一套@2x或是@3x的资源,而不会2套资源都包含。他支持 APP thinning,这属于苹果在应用瘦身方面做的一项优化。
  • 同时提供2x和3x版本(现在没有必要提供1x的了),且图片尺寸严格符合2:3的比例。否则图片的大小和视图的大小不一致,会产生像素不对齐的问题。

四. 总结

资源优化是包体积优化的重头戏,优化的过程中影响面可控,所以落地收益比较容易,经过无用类检测,按需加载和大资源放置远程资源服务器 基本解决存量资源的优化问题,同时建立资源使用规范和相应的检测流水线解决增量问题,严格控制10k以上的资源放入App里,尽量放到远程资源服务器中。同时努力建立完善的增删以及使用机制。本文介绍了关于资源优化的部分,后续我们会针对APP做过的其他的优化进一步详细的描述,敬请期待。