iOS开发者进阶手机移动程序开发

iOS 安装包的瘦身 (持续完善中...)

2019-06-28  本文已影响54人  __Mr_Xie__

前言

随着 app 版本的迭代,app 功能可能会越来越多,此时 app 打出来的包也会越来越大,由于 Apple 对安装包大小的有限制(具体参见 Apple文档),所以这就涉及到app安装包的瘦身。
App Store 安装包是由 资源可执行文件 两部分组成,安装包瘦身也是从这两部分进行。

资源瘦身

资源瘦身主要是去掉 无用资源压缩资源 ,资源包括 图片音视频文件配置文件 以及 多语言 wording。无用资源是指资源在工程文件里,但没有被代码引用。检查方法是,用资源关键字(通常是文件名,图片资源需要去掉 @2x @3x),搜索代码,搜不到就是没有被引用。当然,有些资源在使用过程中是拼接而成的(如 loading_xxx.png ),需要手工过滤。

经验:可以把工具使用和手工过滤想结合。

Use this useful utility tool to check what image resources are not being used in your Xcode projects. Very useful to reduce your bundle size by showing you what images are not used!

优点:工具 Unused脚本 的调用做了封装,通过界面可以配置一定的信息,然后比较清晰的输入结果。
缺点:不够智能,不够通用,速度太慢,结果不正确。

工具二LSUnusedResources
或直接下载 LSUnusedResources.app.zip


LSUnusedResources 很大程度上受 工具 Unused 的影响,比如界面、交互,以及部分代码。但是,本工具在核心代码上做了优化,使其在搜索的速度、结果的正确上都有了很大的提高。

LSUnusedResources工具核心思想,简述如下:

LSUnusedResources匹配结果
工具 Unused 会把大量实际上有使用的资源,当做未使用的资源输出。LSUnusedResources 则不会出现这样的问题,并且使得结果更加优化。举例说明:

icon_tag_0.png
icon_tag_1.png
icon_tag_2.png
icon_tag_3.png

然后用字符串拼接的方式在代码中引用:

NSInteger index = random() % 4;
UIImage *img = [UIImage imageNamed:[NSString stringWithFormat:@"icon_tag_%d", index]];

icon_tag_x.png 是不应该被当做未使用的资源的,只是以一种比较隐晦的方式间接引用了,所以不应该出现在结果列表中,LSUnusedResources 工具做到了。

插曲

在聊可执行文件的瘦身之前,先介绍一下 XcodeLink Map FileLinkMap 文件是 Xcode 产生可执行文件的同时生成的链接信息,用来描述可执行文件的构造成分,包括 代码段(__TEXT)数据段(__DATA) 的分布情况。只要设置 Project -> Build Settings -> Write Link Map FileYES ,并设置 Path to Link Map Filebuild 完后就可以在设置的路径看到 LinkMap 文件了:



注:想要了解更多,具体可以参见我的文章 Xcode的Link Map File

可执行文件瘦身

回到我们的可执行文件瘦身问题,LinkMap 文件可以帮助我们寻找优化点。

1. 无用方法检测思路

以往 C++ 在链接时,没有被用到的类和方法是不会编进可执行文件里。但 Objctive-C 不同,由于它的动态性,它可以通过类名和方法名获取这个类和方法进行调用,所以编译器会把项目里所有 OC 源文件编进可执行文件里,哪怕该类和方法没有被使用到。

结合 LinkMap 文件的 __TEXT.__text ,通过正则表达式( [+|-][.+\s(.+)] ),我们可以提取当前可执行文件里所有 objc类方法和实例方法( SelectorsAll )。再使用 otool 命令 otool -v -s __DATA __objc_selrefs 逆向 __DATA.__objc_selrefs 段,提取可执行文件里引用到的方法名( UsedSelectorsAll ),我们可以大致分析出 SelectorsAll 里哪些方法是没有被引用的( SelectorsAll-UsedSelectorsAll )。扫描脚本( py ):

import os
import re

outPath = "/Users/luph/Documents/sizetj/" #输出目录
mathoFilePaht = "/Users/luph/Documents/sizetj/Pro" #可执行文件
linkmapPath = "/Users/luph/Documents/sizetj/Pro-LinkMap-normal-arm64.txt"
selrefsFile =  outPath+"/selrefs.txt" #引用sel文件
cmd = "otool -v -s __DATA __objc_selrefs "+ mathoFilePaht +" >> "+selrefsFile
os.system(cmd) #逆向selrefs段

linkmapContent = open(linkmapPath,encoding="utf8", errors='ignore').read()
pattern = re.compile(r'[+|-]\[\w+ \w+\]') 
selall = pattern.findall(linkmapContent)

selrefsF = open(selrefsFile,encoding="utf8", errors='ignore')
selrefsList = []
for line in selrefsF.readlines():
    if '__objc_methname' in line:
        line = line.strip("\n");
        lineSplit = line.split(":")
        if  len(lineSplit)  > 0:
            selrefs = ""
            lineSplit.reverse()
            for subStr in lineSplit:
                if len(subStr) > 0:
                    selrefs = subStr
                    break
            if len(selrefs) > 0:
                selrefsList.append(selrefs)
selrefsF.close()   

output = open(outPath+"result.txt", 'w')
for sel in selall:
    print("正在扫描【{0}】".format(sel))
    selMth = sel.replace("+",'')
    selMth = selMth.replace("-",'')
    selMth = selMth.replace("[",'')
    selMth = selMth.replace("]",'')
    selL = selMth.split(" ")
    selMth = selL[1]
    isUse = False
    for selref in selrefsList:
        if  selref == selMth:
            isUse = True
            break 
    if not isUse:
        print("发现无用方法【{0}】".format(sel))
        output.write("{0}\n".format(sel))  
     
output.close()
print("扫描结束")

注:

2. 查找无用oc类

查找无用oc类有三种方式:

3. 扫描重复代码

可以利用第三方工具simian扫描。

4. 其他

从安装包的瘦身角度来说,对于语言选择这块来说,不推荐使用 Swift,不论纯 Swift 还是 混编,任何一个包含有 Swift 代码的 App 都有的一个为了支持 Swift 的动态库集合,在10M 左右。如果你使用 Objective - C 完全不用这个东西。

更多相关

1. App Thinning

据Apple官方文档的介绍,App Thinning主要有三个机制:

然而,一个很常见的误区是认为使用 bitcode 能优化包大小,其实启用 bitcode 作用并不大。实际上 bitcode 和包大小半毛钱关系都没有,它仅仅是把编译的最后一步留给苹果,这样苹果就可以在优化编译器后,再次将我们的应用打包,从而让历史应用也能享受到新技术
文档 里可看到

In fact, app slicing handles the majority of the app thinning process. ‘App Slicing’ feature finally switched on in iOS 9.0.2

说明slicing才是主要处理 app thinning的而且该功能需要在iOS9.0.2以上才支持(iOS9.0中被关闭了,因为一个iCloud的bug)。实际上Bitcode,做的事情是指令集优化。根据你设备的状态去做编译优化,进而提升性能。所以Bitcode对包的大小优化起不到什么本质上的作用。

注意点
1.开启 Bitcode 编译后,编译产生的 .app 体积会变大(中间代码,不是用户下载的包),且 .dSYM 文件不能用来崩溃日志的符号化(用户下载的包是 Apple 服务重新编译产生的,有产生新的符号文件)
2.通过 Archive 方式上传 AppStore 的包,可以在Xcode的Organizer工具中下载对应安装包的新的dSYM符号文件。或者iTunes Connect上下载对应构建包的dSYM(需消除混淆)
详情见 文档

Author

如果你有什么建议,可以关注我的公众号:iOS开发者进阶,直接留言,留言必回。

参考

上一篇 下一篇

猜你喜欢

热点阅读