一. 前言
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的整数倍。
丨2.2 数据段
Mach-O的Data部分,其实是真正存储APP二进制数据的地方,前面的header和load command,仅是提供文件的说明以及加载信息的功能。
Data(数据段): 主要是代码、数据,包含了Load commands中需要的各个段(Segment)的数据,每个Segment可以有多个Section,下面列举一些常见的 Section。在Data(数据段)中,大写的字符串(如__TEXT)代表的是Segment,小写的字符串(如__objc_methtype)代表的是Section。
丨2.3 分析Mach-O文件
可以用MachOView能查看MachO文件信息,如下图所示。
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替我们实现的,我们就不需要服务器了。
以下是按需加载支持的类型
On-Demand Resource的三种标签:
- Initial install tags.此种标签的资源,会随着App从App Store下载而下载,但是会影响App的ipa大小,也就是说此种资源会包含在ipa内。
- Prefetch tag order.此种标签会在App下载后,开始下载相应的资源,下载是存在顺序的,后面会说明。此种资源并不会影响ipa的大小,也就是说此种资源并不包含在ipa内。
- Dowloaded only on demand. 此种标签下的资源,会在必要的时候,主动触发下载,这是我们开发者自己控制下载时机的。
目前项目中大多用到的是第二种按需加载
苹果规定标签用于识别和管理一组按需资源。为项目中的资源分配一个或多个标签会将其识别为按需资源。在运行时,所有管理按需资源的调用都使用标签,而不是单个资源。
可以看到,使用按需加载节省了大约20多MB的包体积。
按需资源生命周期
- app请求tag。
- 本地存储资源文件。
- 托管在appstore上。
- 下载相关资源文件。
- 下载资源文件。
- 保存资源文件。
- app结束使用资源文件tag。
- 释放资源文件。
- 清除资源文件。
启用按需资源
图: 启用按需资源设置。
一个app可以设置多个tag,资源放在对应tag下,点击资源可以看到,如下图
进入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做过的其他的优化进一步详细的描述,敬请期待。