iOS中怎么删除无用的类
创始人
2024-12-19 23:06:32
0次

引用类地址

可以通过Mac自带的工具otool打印Mach-o中的段信息,需要注意的是模拟器和真机对应的可执行文件,数据的存储方式不同需要加以区分。

可以通过file命令获取到arch。

#binary_file_arch: distinguish Big-Endian and Little-Endian#file -b output example: Mach-O 64-bit executable arm64binary_file_arch = os.popen('file -b ' + path).read().split(' ')[-1].strip()

在取类地址的时候区分x86_64和arm。

def pointers_from_binary(line, binary_file_arch): line = line[16:].strip().split(' ') pointers = set() if binary_file_arch == 'x86_64':  #untreated line example:00000001030cec80d8 75 15 03 01 00 00 00 68 77 15 03 01 00 00 00  pointers.add(''.join(line[4:8][::-1] + line[0:4][::-1]))  pointers.add(''.join(line[12:16][::-1] + line[8:12][::-1]))  return pointers #arm64 confirmed,armv7 arm7s unconfirmed if binary_file_arch.startswith('arm'):  #untreated line example:00000001030bcd2003138580 00000001 03138878 00000001  pointers.add(line[1] + line[0])  pointers.add(line[3] + line[2])  return pointers return None

通过otool -v -s __DATA __objc_classrefs获取到引用类的地址。

def class_ref_pointers(path, binary_file_arch):  ref_pointers = set()  lines = os.popen('/usr/bin/otool -v -s __DATA __objc_classrefs %s' % path).readlines()  for line in lines:    pointers = pointers_from_binary(line, binary_file_arch)    ref_pointers = ref_pointers.union(pointers)  return ref_pointers

所有类地址

通过otool -v -s __DATA __objc_classlist获取所有类的地址。

def class_list_pointers(path, binary_file_arch):  list_pointers = set()  lines = os.popen('/usr/bin/otool -v -s __DATA __objc_classlist %s' % path).readlines()  for line in lines:    pointers = pointers_from_binary(line, binary_file_arch)    list_pointers = list_pointers.union(pointers)  return list_pointers

取差集

用所有类信息减去引用类的信息,此时我们可以拿到未使用类的地址信息。

unref_pointers = class_list_pointers(path, binary_file_arch) - class_ref_pointers(path, binary_file_arch)

符号化

通过nm -nm命令可以得到地址和对应的类名字。

def class_symbols(path):  symbols = {}  #class symbol format from nm: 0000000103113f68 (__DATA,__objc_data) external _OBJC_CLASS_$_EpisodeStatusDetailItemView  re_class_name = re.compile('(\w{16}) .* _OBJC_CLASS_\$_(.+)')  lines = os.popen('nm -nm %s' % path).readlines()  for line in lines:    result = re_class_name.findall(line)    if result:      (address, symbol) = result[0]      symbols[address] = symbol  return symbols

过滤

在实际分析的过程中发现,如果一个类的子类被实例化,父类未被实例化,此时父类不会出现在__objc_classrefs这个段里,在未使用的类中需要将这一部分父类过滤出去。使用otool -oV可以获取到类的继承关系。

def filter_super_class(unref_symbols):  re_subclass_name = re.compile("\w{16} 0x\w{9} _OBJC_CLASS_\$_(.+)")  re_superclass_name = re.compile("\s*superclass 0x\w{9} _OBJC_CLASS_\$_(.+)")  #subclass example: 0000000102bd8070 0x103113f68 _OBJC_CLASS_$_TTEpisodeStatusDetailItemView  #superclass example: superclass 0x10313bb80 _OBJC_CLASS_$_TTBaseControl  lines = os.popen("/usr/bin/otool -oV %s" % path).readlines()  subclass_name = ""  superclass_name = ""  for line in lines:    subclass_match_result = re_subclass_name.findall(line)    if subclass_match_result:      subclass_name = subclass_match_result[0]    superclass_match_result = re_superclass_name.findall(line)    if superclass_match_result:      superclass_name = superclass_match_result[0]    if len(subclass_name) > 0 and len(superclass_name) > 0:      if superclass_name in unref_symbols and subclass_name not in unref_symbols:        unref_symbols.remove(superclass_name)      superclass_name = ""      subclass_name = ""  return unref_symbols

为了防止一些三方库的误伤,还可以去过滤一些前缀,或者是是仅保留带有某些前缀的类。

for unref_pointer in unref_pointers:    if unref_pointer in symbols:      unref_symbol = symbols[unref_pointer]      if len(reserved_prefix) > 0 and not unref_symbol.startswith(reserved_prefix):        continue      if len(filter_prefix) > 0 and unref_symbol.startswith(filter_prefix):        continue      unref_symbols.add(unref_symbol)

最终结果保存在脚本目录下。

script_path = sys.path[0].strip()f = open(script_path+"/result.txt","w")f.write( "unref class number:  %d\n" % len(unref_symbles))f.write("\n")for unref_symble in unref_symbles:  f.write(unref_symble+"\n")f.close()

这个思路在一定程度上能够减少代码的冗余,减小包的体积。因为是静态分析,不能包括动态调用的情况,对于需要删除的类需要进一步的确认。

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...