服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - IOS - iOS代码瘦身实践之如何删除无用的类

iOS代码瘦身实践之如何删除无用的类

2021-05-26 16:18yuec IOS

这篇文章主要给大家介绍了关于iOS代码瘦身实践之如何删除无用的类,文中通过示例代码介绍的非常详细,对大家各位iOS开发者们具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

前言

本文将提供一种静态分析的方式,用于查找可执行文件mach-o中未使用的类,源码链接:xuezhulian/classunref 。

mach-o文件中__data __objc_classrefs段记录了引用类的地址,__data __objc_classlist段记录了所有类的地址,取差集可以得到未使用的类的地址,然后进行符号化,就可以得到未被引用的类信息。

引用类地址

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

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

?
1
2
3
#binary_file_arch: distinguish big-endian and little-endian
#file -b output example: mach-o 64-bit executable arm64
binary_file_arch = os.popen('file -b ' + path).read().split(' ')[-1].strip()

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def pointers_from_binary(line, binary_file_arch):
 line = line[16:].strip().split(' ')
 pointers = set()
 if binary_file_arch == 'x86_64':
  #untreated line example:00000001030cec80  d8 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:00000001030bcd20  03138580 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获取到引用类的地址。

?
1
2
3
4
5
6
7
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获取所有类的地址。

?
1
2
3
4
5
6
7
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

取差集

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

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

符号化

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

?
1
2
3
4
5
6
7
8
9
10
11
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可以获取到类的继承关系。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

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

?
1
2
3
4
5
6
7
8
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)

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

?
1
2
3
4
5
6
7
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()

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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对服务器之家的支持。

原文链接:https://juejin.im/post/5d6482b0e51d456209238885

延伸 · 阅读

精彩推荐
  • IOS解析iOS开发中的FirstResponder第一响应对象

    解析iOS开发中的FirstResponder第一响应对象

    这篇文章主要介绍了解析iOS开发中的FirstResponder第一响应对象,包括View的FirstResponder的释放问题,需要的朋友可以参考下...

    一片枫叶4662020-12-25
  • IOSiOS布局渲染之UIView方法的调用时机详解

    iOS布局渲染之UIView方法的调用时机详解

    在你刚开始开发 iOS 应用时,最难避免或者是调试的就是和布局相关的问题,下面这篇文章主要给大家介绍了关于iOS布局渲染之UIView方法调用时机的相关资料...

    windtersharp7642021-05-04
  • IOSIOS 屏幕适配方案实现缩放window的示例代码

    IOS 屏幕适配方案实现缩放window的示例代码

    这篇文章主要介绍了IOS 屏幕适配方案实现缩放window的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要...

    xiari5772021-06-01
  • IOSiOS中tableview 两级cell的展开与收回的示例代码

    iOS中tableview 两级cell的展开与收回的示例代码

    本篇文章主要介绍了iOS中tableview 两级cell的展开与收回的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    J_Kang3862021-04-22
  • IOSIOS开发之字典转字符串的实例详解

    IOS开发之字典转字符串的实例详解

    这篇文章主要介绍了IOS开发之字典转字符串的实例详解的相关资料,希望通过本文能帮助到大家,让大家掌握这样的方法,需要的朋友可以参考下...

    苦练内功5832021-04-01
  • IOS关于iOS自适应cell行高的那些事儿

    关于iOS自适应cell行高的那些事儿

    这篇文章主要给大家介绍了关于iOS自适应cell行高的那些事儿,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的...

    daisy6092021-05-17
  • IOSiOS 雷达效果实例详解

    iOS 雷达效果实例详解

    这篇文章主要介绍了iOS 雷达效果实例详解的相关资料,需要的朋友可以参考下...

    SimpleWorld11022021-01-28
  • IOSiOS通过逆向理解Block的内存模型

    iOS通过逆向理解Block的内存模型

    自从对 iOS 的逆向初窥门径后,我也经常通过它来分析一些比较大的应用,参考一下这些应用中某些功能的实现。这个探索的过程乐趣多多,不仅能满足自...

    Swiftyper12832021-03-03