在LinkMap初探提到了自动分析LinkMap的开源项目,今天来分析一下。
其实源码里面的核心思想挺简单的,就是用一个字典{symbol:size}
,计算总体积的时候,枚举每一个key,相加即可。
一. 读取文件
1. 确保字段完整
def read_base_link_map_file(base_link_map_file, base_link_map_result_file):
try:
link_map_file = open(base_link_map_file)
except IOError:
print "Read file " + base_link_map_file + " failed!"
return
else:
try:
content = link_map_file.read()
except IOError:
print "Read file " + base_link_map_file + " failed!"
return
else:
obj_file_tag_index = content.find("# Object files:")
# +15的意思是读取下一行
sub_obj_file_symbol_str = content[obj_file_tag_index + 15:]
symbols_index = sub_obj_file_symbol_str.find("# Symbols:")
if obj_file_tag_index == -1 or symbols_index == -1 or content.find("# Path:") == -1:
print "The Content of File " + base_link_map_file + " is Invalid."
pass
上述代码是为了确保该文件中存在File段和Symbol段,这样才能开始进行下面的读取文件的操作。
2. 读取Files字段
Files字段长这样:
# Object files:
[ 0] linker synthesized
[ 1] /Users/huanghongbin/Library/Developer/Xcode/DerivedData/TestSuperDemo-bbczgzluwrthgjbpluoujrgxekqa/Build/Intermediates.noindex/TestSuperDemo.build/Debug-iphonesimulator/TestSuperDemo.build/TestSuperDemo.app-Simulated.xcent
[ 2] /Users/huanghongbin/Library/Developer/Xcode/DerivedData/TestSuperDemo-bbczgzluwrthgjbpluoujrgxekqa/Build/Intermediates.noindex/TestSuperDemo.build/Debug-iphonesimulator/TestSuperDemo.build/Objects-normal/x86_64/ViewController.o
[ 3] /Users/huanghongbin/Library/Developer/Xcode/DerivedData/TestSuperDemo-bbczgzluwrthgjbpluoujrgxekqa/Build/Intermediates.noindex/TestSuperDemo.build/Debug-iphonesimulator/TestSuperDemo.build/Objects-normal/x86_64/SonViewController.o
[ 4] /Users/huanghongbin/Library/Developer/Xcode/DerivedData/TestSuperDemo-bbczgzluwrthgjbpluoujrgxekqa/Build/Intermediates.noindex/TestSuperDemo.build/Debug-iphonesimulator/TestSuperDemo.build/Objects-normal/x86_64/main.o
[ 5] /Users/huanghongbin/Library/Developer/Xcode/DerivedData/TestSuperDemo-bbczgzluwrthgjbpluoujrgxekqa/Build/Intermediates.noindex/TestSuperDemo.build/Debug-iphonesimulator/TestSuperDemo.build/Objects-normal/x86_64/AppDelegate.o
[ 6] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.1.sdk/System/Library/Frameworks//Foundation.framework/Foundation.tbd
[ 7] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.1.sdk/usr/lib/libobjc.tbd
[ 8] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.1.sdk/usr/lib/libSystem.tbd
[ 9] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.1.sdk/System/Library/Frameworks//UIKit.framework/UIKit.tbd
我们可以通过这一段来找到序号和文件名的对应,后面分析size的时候就可以以文件名为key,size为value去分析了。
link_map_file_tmp = open(base_link_map_file)
reach_files = 0
reach_sections = 0
reach_symbols = 0
size_map = {}
while 1:
line = link_map_file_tmp.readline()
if not line:
break
if line.startswith("#"):
if line.startswith("# Object files:"):
reach_files = 1
pass
if line.startswith("# Sections"):
reach_sections = 1
pass
if line.startswith("# Symbols"):
reach_symbols = 1
pass
pass
else:
if reach_files == 1 and reach_sections == 0 and reach_symbols == 0:
index = line.find("]")
if index != -1:
symbol = {"file": line[index + 2:-1]}
key = int(line[1: index])
size_map[key] = symbol
pass
上面的代码就是把序号取出来,作为key,把路径取出来,作为value一一对应。
3. Symbols字段
Symbols字段长这样:
# Symbols:
# Address Size File Name
0x100001660 0x00000460 [ 2] -[ViewController init]
0x100001AC0 0x00000040 [ 2] -[ViewController viewDidLoad]
0x100001B00 0x000000B0 [ 2] -[ViewController push]
x100001BB0 0x00000030 [ 2] -[ViewController shouldNotPrint]
0x100001BE0 0x00000020 [ 2] -[ViewController button]
0x100001C00 0x00000040 [ 2] -[ViewController setButton:]
0x100001C40 0x00000020 [ 2] -[ViewController allPlate]
0x100001C60 0x00000040 [ 2] -[ViewController setAllPlate:]
0x100001CA0 0x00000020 [ 2] -[ViewController currentPlateInfo]
0x100001CC0 0x00000040 [ 2] -[ViewController setCurrentPlateInf
可以看到,Symbols字段有十六进制的Size,每一个序号对应一个文件,只要我们把这个序号的Size相加,即可得到这个文件的大小了。
elif reach_files == 1 and reach_sections == 1 and reach_symbols == 1:
symbols_array = line.split("\t")
if len(symbols_array) == 3:
file_key_and_name = symbols_array[2]
size = int(symbols_array[1], 16)
index = file_key_and_name.find("]")
if index != -1:
key = file_key_and_name[1:index]
key = int(key)
# 根据symbols字段里面的key,回到files字段找到对应的.o文件
symbol = size_map[key]
if symbol:
if "size" in symbol:
symbol["size"] += size
pass
else:
symbol["size"] = size
pass
pass
pass
pass
二. 体积计算
1. 计算文件体积
上面读取文件完毕后,已经将所有.o文件的大小都获取到,并存在了size_map
里面,size_map
里的内容如下所示:
size_map = {
"100" : { # 序号
"symbol" : {
"size" : 1024B, # 大小
"file" : "User/Document/File.o" # 路径
}
}
}
其中,路径中的文件不仅仅是.o文件,也有可能是.a文件(包含若干个.o文件),要分析.a文件的大小,则需要把.a文件里面所有的.o文件体积加起来。
.a文件的名字像这样:libAFNetworking.a(AFNetworking-dummy.o),所以要获取.a文件名字,就需要用(分开。
这里我额外加了白名单机制,因为有些文件加入了#ifDebug就会没有size字段,过滤掉免得显示一些警告信息。
for key in size_map:
symbol = size_map[key]
if "size" in symbol:
total_size += symbol["size"]
# .o文件和.a文件,如libAFNetworking.a(AFNetworking-dummy.o)就可以区分开
# 普通的.o文件就会按.o计算
# .a文件会统一计算
o_file_name = symbol["file"].split("/")[-1]
a_file_name = o_file_name.split("(")[0]
if a_file_name in a_file_map:
a_file_map[a_file_name] += symbol["size"]
pass
else:
a_file_map[a_file_name] = symbol["size"]
pass
pass
else:
o_file_name = symbol["file"].split("/")[-1]
if o_file_name in debug_white_list:
pass
else:
print "WARN : some error occurred for [file, key] : ",
print "[" + o_file_name + ", " + key.__str__() + "]"
2. 体积大小排序
# 根据size从大到小排序
a_file_sorted_list = sorted(a_file_map.items(), key=lambda x: x[1], reverse=True)
这个排序的意思是,从大到小,根据a_file_map的value(即size)排序。
3. 打印文件
print "%s" % "=".ljust(80, '=')
print "%s" % (base_link_map_file+"各模块体积汇总").center(87)
print "%s" % "=".ljust(80, '=')
if os.path.exists(base_link_map_result_file):
os.remove(base_link_map_result_file)
pass
print "Creating Result File : %s" % base_link_map_result_file
output_file = open(base_link_map_result_file, "w")
for item in a_file_sorted_list:
print "%s%.2fM" % (item[0].ljust(50), item[1]/1024.0/1024.0)
output_file.write("%s \t\t\t%.2fM\n" % (item[0].ljust(50), item[1]/1024.0/1024.0))
pass
print "%s%.2fM" % ("总体积:".ljust(53), total_size / 1024.0/1024.0)
print "\n\n\n\n\n"
output_file.write("%s%.2fM" % ("总体积:".ljust(53), total_size / 1024.0/1024.0))
link_map_file_tmp.close()
output_file.close()
现在获得的size单位是B,要转化为MB,则需要除以1024的平方,最后输出打印即可(他还顺便生成了一个结果文件)。
三. 调用
1. 分析单个文件
直接调用了read_base_link_map_file
函数,输入为LinkMap文件路径、输出结果路径。
2. 对比分析两个文件
对比的文件也调用了read_base_link_map_file
方法,输出两个output_file
,格式类似于:
================================================================================
demoData/BaseLinkMap.txt各模块体积汇总
================================================================================
Creating Result File : demoData/BaseLinkMapResult.txt
AppDelegate.o 0.01M
ViewController.o 0.00M
TestCleanPackage.app.xcent 0.00M
UnUsedClass.o 0.00M
main.o 0.00M
libobjc.tbd 0.00M
linker synthesized 0.00M
Foundation.tbd 0.00M
UIKit.tbd 0.00M
总体积: 0.01M
然后对各个文件体积和总体积进行分词(其他字段忽略),并存入了字典:
def parse_result_file(result_file_name):
base_bundle_list = []
result_file = open(result_file_name)
while 1:
line = result_file.readline()
if not line:
break
bundle_and_size = line.split()
if len(bundle_and_size) == 2 and line.find(":") == -1:
bundle_and_size_map = {"name": bundle_and_size[0], "size": bundle_and_size[1]}
base_bundle_list += [bundle_and_size_map]
pass
return base_bundle_list
最后compare
方法将两个文件的字典的size
取出来,进行对比,如果后者文件更大点则输出,如果后者有而前者没有也会输出。
网友评论