iOS App 瘦身

1. 前⾔

随着需求的增加,App 的安装包的⼤⼩通常会不断上涨。之前我做的一个 App ipa 的⼤⼩达到了300 MB。解压后的包⼤⼩达到了400 MB。

其中,主 App 的可执⾏⽂件占 200 MB。第三⽅ framework 的可执⾏⽂件占 100 MB。App 本身的资源⽂件占 30 MB。第三方的资源⽂件占 20 MB。App 的 Extension 占 50 MB。

IPA App Bundle Main / Frameworks Executable Assets.car PlugIns
300 M 400 M 200 M / 100 M 30 M + 20 M (三方 SDK) 50M

将编译后的 App 上传到 Test Flight 后,可以查看 app 在各个设备上的预估安装⼤⼩。⽤户在 AppStore 上看到的⼤⼩就是 Test Flight 上对应设备的 Install Size。

如果包太⼤,可能会让⼀些⼿机容量较⼩的⽤户对我们的App望⽽却步。所以减⼩包⼤⼩成为了刻不容缓的任务。

⾸先看⼀下App⾥主要包含了哪些内容:

  • Exectutable: 编译后的可执⾏⽂件

  • Resources:图⽚、⾳频、视频等资源⽂件

  • Framework:使⽤到的动态库

  • Pulgins:App的Extensions

Framework中主要是第三⽅的库,我们能做的事情不多。我们可以从 Exectutable、Resources、Pulgins 这三块⼊⼿,进⾏App的瘦身

2. 可执⾏⽂件优化

1. 优化编译选项

由于⼀些历史原因,之前的 App 出 release 包的时候,保留了symbols。这样在外部⽤户使⽤App过程中如果出现crash,我们是可以 直接通过代码输出符号化的crash的堆栈信息。

常⻅的获取堆栈信息有这三种⽅式:

通过NSThread的callStackSymbols⽅法

[NSThread callStackSymbols]

通过backtrace_symbols函数

 void* callstack[128];

int frames = backtrace(callstack, 128);

char **strs = backtrace_symbols(callstack, frames);

借助第三⽅库BSBacktraceLogger

BSBacktraceLogger.bs_backtraceOfMainThread()

要剔除 symbol 也很简单:在 Project 的 Build Settings 中,修改编译选项。然后让每个 Target 继承 Project 的选项,就可以达到⽬的。先上结论:

选项 修改前 修改后
Strip Linked Product main app: NO extension: YES main app: YES extension: YES
Strip Style main app: Debugging Symbols main app: All Symbols

下⾯介绍⼀下三个选项的含义。

1. deployment postprocessing

If enabled, indicates that binaries should be stripped and file mode, owner, and group information should be set to standard values.

这个选项是部署的总开关。默认是No,我们维持默认值即可。在debug阶段,Xcode会帮我们保留debug symbol等各种调试信息。当我们使⽤Archive打包的时候,Xcode会⾃动将deployment postprocessing设置为Yes。

2. Strip Linked Product

If enabled, the linked product of the build will be stripped of symbols when performing deployment postprocessing.

这个选项需要在deployment postprocessing为Yes时才会⽣效。它主要⽤来控制是否要把⾮必要的符号信息剔除掉。Xcode默认值是Yes,我们不⽤担⼼在debug时symbols会被剔除。因为在debug阶段deployment postprocessing默认是No,所以Strip Linked Product选项将被忽略。

在我之前 App 的版本中,Strip Linked Product值为No。也正是这样,导致了release包中包含的debug symbols。这让App增⼤了⼏⼗MB。

因此我们需要针对Appstore的包,将其改为Yes。

3. Strip Style

The level of symbol stripping to be performed on the linked product of the build. The default value is defined by the targetʼs product type.

Strip Style⽤来控制需要去除的符号的类型:

  • All Symbols: 剔除所有符号。

  • Non-Global Symbols: 剔除⾮全局的 Symbol,保留全局符号。建议在静态库、动态库、Extension中使⽤这个选项。

  • Debug Symbols: 剔除调试符号,将导致⽆法断点调试。

Xcode默认是All Symbols。在我之前App中,main app是Debug Symbols。我们需要将main App的选项改为All Symbols。

2. 堆栈符号化

有的同学可能会担⼼开启Strip Linked Product,并将Strip Style改为All Symbols后,外部⽤户如果出现crash怎么办。其实Xcode在打包的时候,会⽣成dSYM⽂件,这个⽂件⾥⾯包含了App的符号信息。所以我们依然可以通过dSYM来还原我们的堆栈符号信息。

但是如果你有在代码中,通过诸如 [NSThread callStackSymbols] 等⽅式获取堆栈信息,那拿到的将是不含符号信息的堆栈。需要我们通过atos⼯具做进⼀步的解析。

这⾥简单介绍⼀下如何⼿动解析:

  • 获取dSYM⽂件

我们需要拿到dSYM⽂件,⽐如到jenkins下载对应的dSYM⽂件。

  • 获取堆栈信息

从app中获取类似下⾯这种格式的堆栈信息

 0 Demo        0x0000000104ff891c Demo + 18716
  • 计算load address

堆栈信息中的0x0000000104ff891c是⼗六进制的真实内存地址。18716是⼗进制的偏移地址。这⾥我们需要额外计算load address。计算⽅式是:真实地址-偏移地址

0x0000000104ff891c - 0x491c(18716的⼗六进制) = 0x104FF4000.

所以这⾥我们得到的load address是0x104FF4000。

  • 打开终端,使⽤atos解析
atos -arch <arch> -o <path_to_dsym> -l <load_address> <stack_address_in_crash_report>

在这个例⼦⾥,我们使⽤以下指令:

atos -arch arm64 -o Demo.app.dSYM/Contents/Resources/DWARF/Demo -l 0x104FF4000 0x0000000104ff891c

⼀顿操作后,我们拿到了最终符号化后的堆栈信息

ViewController.test() (in Demo) (ViewController.swift:30)

这⾥需要⼀提的是,⼿动计算load address可能出现不准确的情况。所以在我们App的⽇志⾥,已经加⼊了load address信息,类似这样:

Load address: 4330029056

3. 扫描并删除⽆⽤的代码

如果项⽬是使⽤Objective-C开发,可以使⽤AppCode IDE扫描没⽤的类。但是AppCode不⽀持扫描Swift的没⽤的类。感谢开源社区,我们还有另外⼀个开源的⼯具fus,可以很⽅便的扫描Swfit中没⽤的类。

安装fus:

gem install fus

查看帮助

fus help

查找当前⽬录下⽆⽤的类

fus find

最后根据输出⼿动清理代码即可。我在实际操作过程中,发现fus存在误报的情况。项⽬中明明有在⽤的类,fus也会认为是⽆⽤的。不过所幸误报的数量较少,我们清理代码前逐⼀确认⼀遍即可。

4. 分析LinkMap

⼀个⼤型的项⽬,只是代码段就有可能超过100M。这时候检查到底是哪个类、哪个第三⽅库占⽤了太多空间,就显得尤为重要。

我之前写过⼀个LinkMap的分析⼯具:https://github.com/huanxsd/LinkMap。⽤来分析项⽬的LinkMap⽂件,得出每个类或者库所占⽤的空间⼤⼩(代码段+数据段),⽅便定位需要优化的类或静态库。

在我们这个项⽬中,LinkMap解析出来的结果是c++的代码占了⼤部分空间。这样以后就可以针对性的对这部分代码进⾏优化。

[图片上传失败...(image-20bddf-1723110030447)]

经过以上⼏种⽅法处理后,可执⾏⽂件从200 MB减⼩到了 110 MB。

3. 资源⽂件优化

1. 删除⽆⽤的资源⽂件

这部分其实⽹上已经很多教程了,主要就是靠⼯具来查找所有使⽤到的字符串和所有的资源⽂件。两者进⾏匹配。流⾏的有这两个

⼯具:

FengNiao

LSUnusedResources

分别⽤这两个⼯具进⾏查找,结果基本⼀样。需要注意的是,如果代码中的图⽚名称是通过动态拼接的,那有可能造成匹配失败,出现误报的情况。所以在使⽤⼯具搜索完,还是需要仔细的确认⼀遍是否真的没有使⽤,再清理图⽚。

2. 压缩图⽚⼤⼩

压缩图⽚分两种,⼀种是⽆损压缩,⼀种是有损压缩。在Jerold的提示下,我使⽤Pngyu先进⾏了有损压缩,再⽤ImageOptim进⾏⽆损压缩。

3. 删除Localizable.strings中没有使⽤的字符串

我们项⽬中的多语⾔⽂件是通过python脚本,通过扫描项⽬代码,提取出来的。但是之前的python脚本有可以优化的空间。⽐如⼀ 些代码⾥不再使⽤的多语⾔字符串⼀直保留在Localizable.strings中。我们可以通过优化python脚本,可以删除这些字符串。具体的 实现可以看项⽬中的cleanUpUnusedStrings.py⽂件。

经过上⾯三个⽅法处理后。App包中的资源⽂件⼤⼩从原来的30 MB降到了20MB。

4. 针对部分特别⼤的png图⽚,使⽤ jpg 或者 webp 格式代替

在调研过程中,发现⼀个问题,有时候我们⼯程中的图⽚很⼩,但是ipa中的图⽚却变⼤了⾮常多。⽐如有⼀张图⽚⼯程中是325KB。但是编译后,在ipa中变成了1.3MB。⾜⾜⽐原来⼤了4倍。这是因为 Xcode在打包的时候,会把png图⽚转换成更适合iphone的图⽚,提⾼iphone加载图⽚的速度。代价就是图⽚可能会增⼤。在这篇⽂章中有做说明Xcode's built-in (de)optimization

Xcode for iOS by default converts all PNG images to a non-standard iOS-specific PNG derivative. This format saves iOS devices a trivial conversion step during loading, because it uses premultiplied BGRA instead of RGBA color space. It doesn't affect rendering speed at all.

Xcode's conversion is applied even when it makes files bigger. It will undo ImageOptim's savings.

如果是⼀些不常⽤的图⽚,其实是没必要为了时间⽽牺牲空间的。所以针对那些图⽚很⼤,使⽤频率很低的图⽚,可以考虑转成 jpg 或者 webp格式。这样就可以避免图⽚打包后增⼤的问题。

4. Extension优化

1. 使⽤独⽴的Localizable.strings⽂件

在我之前 App 版本中,有3个Extension直接使⽤了main app的Localizable.strings。由于Extension的资源是各⾃独⽴的,所以会导致打包后,每个Extension都有⼀份完整的Localizable.strings。⼀份完整的多语⾔⽂件有12MB。多出3份多语⾔⽂件相当于App增⼤了3*12=36MB。

解决⽅案是Extension不再使⽤main app的Localizable.strings,每个Extension增加⾃⼰的Localizable.strings⽂件。然后编写python脚本。在导⼊多语⾔时,扫描Extension的代码,拷⻉每个Extension中真正⽤到的字符串。

2. 剔除多余的三⽅库

Extension的代码是完全独⽴的,不能和main app共享。所以Extension在引⼊第三⽅依赖的时候要格外注意。

在我之前App中,有⼀个Extension依赖了完整的ZoomSDK。但是实际上只需要使⽤ZoomSDK⾥的MobileRTCScreenShare.framework。

我们内部有对ZoomSDK做简单的封装,所以通过修改zoomSDK.podspec。增加两个 subspec:

  • MobileRTC: 供main app使⽤的framework

  • MobileRTCScreenShare:供Broadcast Extension使⽤的framework

通过以上两个⽅法,Extension从原来的60MB减⼩到了6MB。

5. 其他优化

1. 重新编译FFmpeg库

我们项⽬中有⽤到FFmpeg⾥的⼀个视频格式转换功能,把3gp⽂件转成mp4⽂件。在之前的版本,项⽬中引⼊了完整的ffmpeg库。a⽂件有154.8MB。由于ffmpeg是⼀个强⼤的库,有⾮常多功能。⽽我们仅仅只需要3gp⽂件转mp4⽂件功能,所以可以通过 对ffmpeg重新编译,剔除掉多余的功能,达到减⼩包⼤⼩的⽬的。

有⼀位⼤佬对ffmpeg做了封装:ffmpeg-kit。通过这个库,我们可以很⽅便的在iOS项⽬中使⽤ffmpeg命令⾏。

所以我们可以在ffmpeg-kit的基础上,重新编译FFmpeg库:

⾸先,修改ffmpeg的编译选项,禁⽤不需要的功能。打开ffmpeg-kit⾥的scripts/apple/ffmpeg.sh,在./configure的后⾯增加以下配置:

--disable-ffplay \
--disable-ffprobe \
--disable-network \
--disable-hwaccels \
--disable-bsfs \
--disable-indevs \
--disable-outdevs \
--disable-devices \
--disable-parsers \
--disable-protocols \
--enable-protocol=file \
--disable-encoders \
--enable-encoder=mpeg4,aac* \
--disable-muxers \

然后执⾏ios.sh脚本:

./ios.sh --disable-armv7 --disable-armv7s --disable-i386 --disable-x86-64-mac-catalyst --disable-arm64-mac-catalyst -- disable-arm64e -x

这⾥我们通过--disable-xxx禁⽤不需要的架构,-x 表示⽣成xcframework。最后在prebuild⽬录下,可以找到ffmpeg-kit-min-4.4- ios-xcframework,就是我们需要的库。

重新编译后的库⽂件从原来的154.8MB减⼩到90MB。App的Install Size减⼩了3MB。

结束

以上便是优化的全部⽅法。最终效果也⽐较明显。以iPhone 12为例,App的Install Size从390 MB,降到了现在230 MB。⼀共减⼩了160 MB。

Universal Download Size Universal Install Size iPhone12 Download Size iPhone 12 Install Size
优化前 260 MB 430 MB 140 MB 390 MB
优化后 200 MB 260 MB 90 MB 230 MB
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 问题: App Store规定安装包大小超过150MB的App只能在WIFI环境下载。现在项目App包已经超过这条...
    keyser_fayee阅读 2,848评论 0 10
  • 资源优化 1. 去除无用资源 一般都是版本迭代过程中存在的图片资源。可以借助三方工具来解决: Unused-mas...
    RobinZhao阅读 2,173评论 2 24
  • 缩减iOS安装包大小是很多中大型APP都要做的事,一般首先会对资源文件下手,压缩图片/音频,去除不必要的资源。这些...
    buptwsg阅读 1,962评论 0 8
  • 随着Apple不断更新系统,Xcode不断升级,我们会发现打包出来的App包文件会越来越大。我司最近导入了百度语音...
    麦子_KB阅读 572评论 0 1
  • 安装包组成: 谈到 App 瘦身,最直接的想法莫过于分析一个安装包内部结构,了解其每一部分的来源。解压一个 ipa...
    孔雨露阅读 3,271评论 1 7