找回密码
 会员注册
查看: 15|回复: 0

百度APPiOS端包体积50M优化实践(四)代码优化

[复制链接]

2万

主题

0

回帖

7万

积分

超级版主

积分
71080
发表于 2024-10-8 19:13:24 | 显示全部楼层 |阅读模式
一.?前言百度APP iOS端包体积优化系列文章的前三篇重点介绍了包体积优化整体方案、图片优化和资源优化,图片优化是从无用图片、Asset Catalog和HEIC格式三个角度做深度优化,资源优化包括大资源优化、无用配置文件和重复资源优化,本文重点介绍代码优化,在百度APP实践中,代码优化包括无用类优化、无用模块瘦身、无用方法瘦身、精简重复代码、工具类瘦身和AB实验固化。在代码优化过程,需要分析Mach-O和Link Map,在前面的文章我们已经针对Mach-O文件做过了分析,本文先介绍Link Map文件,然后再详细介绍代码优化方案。百度APP iOS端包体积优化实践系列文章回顾: ?《百度APP iOS端包体积50M优化实践(一)总览》:https://mp.weixin.qq.com/s/ANbFzg7X932o-iDpa8FcxQ《百度APP iOS端包体积50M优化实践(二) 图片优化》:https://mp.weixin.qq.com/s/RR7sjhkuTFgUp7S5E8ECMw《百度APP iOS端包体积50M优化实践(三) 资源优化》:https://mp.weixin.qq.com/s/FQWCX0wkK6ifHQ8RhmmPsg二.?Link Map文件详解丨2.1?简介Link Map 是 Mach-O 格式的二进制文件的一种辅助文件,它描述了可执行文件的全貌,包括编译后的每一个目标文件的信息以及它们在可执行文件中的代码段、数据段存储详情。通过Link Map文件,我们可以知道可执行文件的路径、CPU架构、目标文件、符号等信息,分析可执行文件中哪个类或库占用比较大,进行安装包瘦身,此外,我们可以清楚地了解可执行文件的内部结构和各个目标文件在其中的位置关系,这对于分析和调试非常有帮助。丨2.2 生成linkMap文件Xcode -> Project -> Build Settings -> Write Link Map File选项值设为yes,Path to Link Map File设置为指定好的LinkMap文件存储位置。丨2.3 LinkMap文件结构解析丨2.3.1?基础信息# Path: /Users/richard/Desktop/demo/DerivedData/demo/Build/Products/Debug-iphoneos/demo.app/demo# Arch: arm64Path是可执行文件的路径,Arch是架构类型。丨2.3.2?Object文件列表Object文件列表列出了所有编译后的目标文件,包括.o文件和dylib库。每个目标文件都有一个对应的编号,上图第一列就是,通过该编号可以对应到具体的类,在后面的Symbols部分,还会用到此编号。丨2.3.2?Section段表Section段表描述了各个段在最终编译成的可执行文件中的偏移位置及大小,包括代码段(TEXT)和数据段(DATA)。段表中第一列是数据在文件的偏移位置,第二列是Section占用大小,第三列是Segment类型,第四列是Section类型,关于Segment和Section,在前面文章对于Mach-O详解做过介绍,这儿不再赘述。丨2.3.4?SymbolsSymbols模块给出了类里面的方法在内存具体情况。其中第一列是方法起始地址,通过这个地址我们可以查上面的段表;第二列是大小,通过这个可以算出方法占用的大小;第三列是归属的类(.o),值是具体编号,通过反查目标文件列表可以知道对应的类;第四列是方法名称;通过Symbols模块我们可以分析出来每个类对应方法的大小。三.?代码优化丨3.1?无用类瘦身丨3.1.1?静态检测获取无用类方案介绍所谓的静态检测,就是分析linkmap文件和Mach-o文件,Mach-o文件中__DATA __objc_classlist段记录了所有类的地址,__DATA __objc_classrefs段记录了引用类的地址,取差集可以得到未使用的类的地址,然后进行符号化,就可以得到未被引用的类信息。第一、获取所有类地址,命令:otool -v -s __DATA __objc_classlist。otool -v -s __DATA __objc_classlist /Users/ycx/Desktop/demo.app/demoContents?of?(__DATA,__objc_classlist)?section0000000100008238??00009980?00000001?000099d0?000000010000000100008248??00009a48?00000001?00009a98?000000010000000100008258??00009ac0?00000001?00009b38?00000001第二、获取引用类的地址,命令:otool -v -s __DATA __objc_classrefs。otool -v -s __DATA __objc_classrefs /Users/yangchengxu/Desktop/demo.app/demoContents?of?(__DATA,__objc_classrefs)?section000000010000990000000000?00000000?00000000?00000000000000010000991000000000?00000000?000099d0?00000001000000010000992000000000?00000000?00000000?00000000第三、取差集,所有类的地址减去引用类的地址,拿到的就是未使用类的地址信息。第四、符号化,遍历Linkmap和Mach-O文件可获取地址信息对应的具体类名,建立类和地址的映射关系,通过地址反解析出类名。优缺点优点:检测方式简单易行。缺点:对于存在引用关系但根本不会被调用的类,是无法被判断为无用类的。随着版本迭代,新老员工工作交接,很多功能的入口已经不存在了,相关的类也根本不会被调用,但是引用关系仍然保留。通过静态检测的方式,无法检测出这种情况。静态检测无法适用于通过反射调用类及方法的场景。因为静态检测无法感知运行时的环境,无法预测哪些类或方法会被反射调用。因此,在这种情况下,静态检测将无法准确地检测出无用类或无用的方法。丨3.1.2?动态检测获取无用类方案介绍我们知道OC类结构有个isa指针,指向该类的原类meta-class,通过阅读objc源代码,我们发现在meta-class的class_rw_t结构体中的一个flag标志位,flags 的1<<29位标识当前类在运行时中是否被初始化过,参考源码路径://>flags// These are not emitted by the compiler and are never used in class_ro_t. // Their presence should be considered in future ABI versions.// class_t->data is class_rw_t, not class_ro_t#define RW_REALIZED (1<<31)// class is unresolved future class#define RW_FUTURE (1<<30)// class is initialized#define RW_INITIALIZED (1<<29)// class is initializing#define RW_INITIALIZING (1<<28)//>ro is heap copy of class_ro_t#define RW_COPIED_RO (1<<27)// class allocated but not yet registered#define RW_CONSTRUCTING (1<<26)// class allocated and registered#define RW_CONSTRUCTED (1<<25)// available for use; was RW_FINALIZE_ON_MAIN_THREAD// #define RW_24 (1<<24)// class +load has been called#define RW_LOADED (1<<23)由此,检测类是否被初始化的方法如下所示:#define W_INITIALIZED (1<<29)bool> repeat.xml检测结果如下所示,其中duplication标签中的lines表示重复内容的行数,file标签表示从那一行开始重复及具体重复文件路径,codefragment标签表示重复的代码。 *************************** 丨3.5?工具方法瘦身日常开发工程中我们都要使用各种工具方法,常用的实现方式有如下两种实现系统类的Category,如NSDate、UIImage、NSArray、NSDictionary的分类方法实现;独立封装;App在初始开发阶段都有commonTools模块,用来存放各种工具方法,但是随着版本迭代和人员变动,业务也越来越复杂,新来的同学不知道底层模块已经实现了类似方法,为了开发方便会在自己的模块再集成一套,这样导致的结果是工具方法重复建设,此模块瘦身主要目的就是挖掘重复工具方法并优化,百度APP实践过程中主要从以下两个角度入手。遍历LinkMap文件,挖掘出重复的Category,参考以下的脚本代码来实现此功能:def get_files_map(base_link_map_file): link_map_file = open(base_link_map_file, 'rb') reach_files = 0 reach_sections = 0 reach_symbols = 0 files_map = {} while 1: line = link_map_file.readline() line = line.decode('utf-8', errors='ignore') if not line: break if line.startswith("#"): if line.startswith("# Object files:"): reach_files = 1 if line.startswith("# Sections"): reach_sections = 1 if line.startswith("# Symbols"): reach_symbols = 1 else: if reach_files == 1 and reach_sections == 0 and reach_symbols == 0: # files index = line.find("]") if index != -1: symbol = {"file": line[index + 2:-1]} key = int(line[1: index]) files_map[key] = symbol pass link_map_file.close() return files_map对于非Category的工具方法,进行排查和合并,最终下沉到统一工具库里面。丨3.6?AB实验固化 ? ? ?在APP开发过程中,为了更加有效地验证新开发功能的实际效果,我们会进行AB实验,通常会将实验组和对照组分开,并在实验组中进行某种操作,而在对照组中不进行该操作,我们会观察这个操作对实验变量的影响,以确定该操作是否对实验结果产生显著影响。 ? ? ?像百度APP这种日活过亿的应用,每个版本会有10个左右AB实验,一年有240个AB实验,随着长时间的版本迭代,会积累大量AB实验代码,但实际上只有一个分支的代码是线上生效的,另一个分支代码是不会被执行的,所以推进AB实验固化,去除无效分支的代码可以实现减少包体积的目的。百度APP推进AB实验固化分为三个步骤,第一 、从AB实验平台获取已经固化的开关;第二、开发工具判断此实验对应的开关是否在代码中存在;第三、分发给负责的开发同学固化AB实验,删除不用的代码。其中第二步的实现非常关键,就是判断一个开关是否仍有对应的代码逻辑,百度APP采用的方案是获取所有可能使用开关的字符串集合,然后判断第一步拿到的开关是否在集合中,若在说明该开关的对应的实验需要做固化操作。在Objective-C的.h和.m文件中,我们经常用如下代码来定义一个AB开关,然后再后续代码中引用。#define kFaceverifyResourceOptimizeABTestKey @"face_verify_resource_optimize_enable"针对Objective-C的.h和.m的文件内容,用正则过滤,匹配表达式为 @"(.*?)",即可获取所有可能加载开关的字符串集合。同样道理,在Swift文件我们通常通过如下代码来定义一个AB开关,然后再后续代码中引用,加载方式完全一样,针对Swift这种文件,正则表达式应为"(.?)"。 static let verifyResourceOptimizeABTestKey: String = "face_verify_resource_optimize_enable"四.?总结代码优化同样也是包体积优化的重头戏,但跟图片和资源优化相比较,代码修改影响范围大,再加上OC语言动态调用方式多种多样,这导致代码的删除操作更容易引起质量问题,所以优化收益落地难度比较大。百度APP在优化实践过程中挖掘出20M的收益,经过两个季度仅落地8M左右,剩余部分还需要继续推动。 ? ? 本文首先对LinkMap文件格式做了详细介绍,然后对百度APP代码优化方案(无用类优化、无用模块瘦身、无用方法瘦身、精简重复代码、工具类瘦身和AB实验固化)做了系统阐释,后续我们会针对其他优化详细介绍其原理与实现,敬请期待。五.?参考链接[1]、PMD介绍:https://pmd.github.io/[2]、PMD CPD使用方法:https://pmd.sourceforge.io/pmd-5.5.1/usage/cpd-usage.html[3]、XNU源码:https://github.com/apple/darwin-xnu[4]、objc源码:https://github.com/apple-oss-distributions/objc4/tags
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-9 05:50 , Processed in 0.962251 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表