来源:百度Geek说
一、前言
GEEK TALK
百度APP iOS端包体积优化实践系列文章回顾:
《百度APP iOS端包体积50M优化实践(一)总览》
《百度APP iOS端包体积50M优化实践(二) 图片优化》
《百度APP iOS端包体积50M优化实践(三) 资源优化》
二、Link Map文件详解
GEEK TALK
2.1 简介
2.2 生成linkMap文件

2.3 LinkMap文件结构解析
# Path: /Users/richard/Desktop/demo/DerivedData/demo/Build/Products/Debug-iphoneos/demo.app/demo# Arch: arm64


第一列是方法起始地址,通过这个地址我们可以查上面的段表;
第二列是大小,通过这个可以算出方法占用的大小;
第三列是归属的类(.o),值是具体编号,通过反查目标文件列表可以知道对应的类;
第四列是方法名称。

三、代码优化
GEEK TALK
3.1 无用类瘦身
第一、获取所有类地址,命令: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文件可获取地址信息对应的具体类名,建立类和地址的映射关系,通过地址反解析出类名。
对于存在引用关系但根本不会被调用的类,是无法被判断为无用类的。随着版本迭代,新老员工工作交接,很多功能的入口已经不存在了,相关的类也根本不会被调用,但是引用关系仍然保留。通过静态检测的方式,无法检测出这种情况。
静态检测无法适用于通过反射调用类及方法的场景。因为静态检测无法感知运行时的环境,无法预测哪些类或方法会被反射调用。因此,在这种情况下,静态检测将无法准确地检测出无用类或无用的方法。
// Values for class_rw_t->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)// class_rw_t->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 isinitialized() { return getMeta() -›data()-›flags & W_INITIALIZED;}3.2 无用模块瘦身
def find_class(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: index = line.find("]") if index != -1: tmpfile = line[index + 2:-1] file = tmpfile.split("/")[-1] frameworkIndex = file.find("(") if frameworkIndex!= -1: frameworkName = file[0: frameworkIndex] className = file[frameworkIndex + 1:len(file)-1] if files_map: if frameworkName in files_map: files_map[frameworkName] = files_map[frameworkName] + " , " + className else: files_map[frameworkName] = className else: files_map[frameworkName] = className link_map_file.close() return files_map3.3 无用方法瘦身
3.4 精简重复代码
brew install pmd
//其中,--files 用于指定文件目录,--minimum-tokens 用于设置最小重复代码阈值,--format 用于指定输出文件格式,支持 xml/csv/txt 等格式,这里建议使用 xml,方便查看 //生成的 XML 文件内容如下,根据 file 标签信息就能定位到重复代码位置。pmd cpd --files 扫描文件目录 --minimum-tokens 70 --language objectivec --encoding UTF-8 --format xml > repeat.xml检测结果如下所示,其中duplication标签中的lines表示重复内容的行数,file标签表示从那一行开始重复及具体重复文件路径,codefragment标签表示重复的代码。<pmd-cpd> <duplication lines="16" tokens="162"> <file begintoken="16933" column="33" endcolumn="4" endline="28" endtoken="17094" line="13" path="path1"> <file begintoken="23979" column="47" endcolumn="4" endline="26" endtoken="24140" line="11" path="path2" /> <codefragment> *************************** </codefragment> </duplication></pmd-cpd>
3.5 工具方法瘦身
独立封装;
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实验固话
#define kFaceverifyResourceOptimizeABTestKey @"face_verify_resource_optimize_enable"
static let verifyResourceOptimizeABTestKey: String = "face_verify_resource_optimize_enable"
四、总结
GEEK TALK
END
参考资料:
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70027824/viewspace-2964807/ ,如需转载,请注明出处,否则将追究法律责任。