iOS代碼瘦身實(shí)踐之如何刪除無用的類
前言
本文將提供一種靜態(tài)分析的方式,用于查找可執(zhí)行文件Mach-o中未使用的類,源碼鏈接:xuezhulian/classunref (本地下載)。
Mach-o文件中__DATA __objc_classrefs段記錄了引用類的地址,__DATA __objc_classlist段記錄了所有類的地址,取差集可以得到未使用的類的地址,然后進(jìn)行符號化,就可以得到未被引用的類信息。
引用類地址
可以通過Mac自帶的工具otool打印Mach-o中的段信息,需要注意的是模擬器和真機(jī)對應(yīng)的可執(zhí)行文件,數(shù)據(jù)的存儲方式不同需要加以區(qū)分。
可以通過file命令獲取到arch。
#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()
在取類地址的時候區(qū)分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: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獲取到引用類的地址。
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命令可以得到地址和對應(yīng)的類名字。
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
過濾
在實(shí)際分析的過程中發(fā)現(xiàn),如果一個類的子類被實(shí)例化,父類未被實(shí)例化,此時父類不會出現(xiàn)在__objc_classrefs這個段里,在未使用的類中需要將這一部分父類過濾出去。使用otool -oV可以獲取到類的繼承關(guān)系。
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)
最終結(jié)果保存在腳本目錄下。
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()
這個思路在一定程度上能夠減少代碼的冗余,減小包的體積。因?yàn)槭庆o態(tài)分析,不能包括動態(tài)調(diào)用的情況,對于需要刪除的類需要進(jìn)一步的確認(rèn)。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對我們的支持。
上一篇:ios設(shè)備使用iframe寬度超出屏幕的解決方法
欄 目:IOS
本文標(biāo)題:iOS代碼瘦身實(shí)踐之如何刪除無用的類
本文地址:http://www.jygsgssxh.com/a1/IOS/11873.html
您可能感興趣的文章
- 01-11iOS常用算法之兩個有序數(shù)組合并(要求時間復(fù)雜度為0(n))
- 01-11iOS 彈幕功能的實(shí)現(xiàn)思路圖解
- 01-11iOS調(diào)試Block引用對象無法被釋放的小技巧分享
- 01-11iOS動態(tài)更換Icon的全過程記錄
- 01-11iOS實(shí)現(xiàn)文本分頁的方法示例
- 01-11iOS常見宏理解及使用方法
- 01-11iOs遷至WKWebView跨過的一些坑
- 01-11iOS模擬中獎名單循環(huán)滾動效果
- 01-11Python一鍵查找iOS項(xiàng)目中未使用的圖片、音頻、視頻資源
- 01-11iOS中如何獲取某個視圖的截圖詳析


閱讀排行
本欄相關(guān)
- 01-11UILabel顯示定時器文本跳動問題的解決
- 01-11iOS常用算法之兩個有序數(shù)組合并(要
- 01-11iOS 彈幕功能的實(shí)現(xiàn)思路圖解
- 01-11詳解MacOs免密登錄CentOs操作步驟
- 01-11iOS動態(tài)更換Icon的全過程記錄
- 01-11iOS調(diào)試Block引用對象無法被釋放的小技
- 01-11iOS常見宏理解及使用方法
- 01-11iOS實(shí)現(xiàn)文本分頁的方法示例
- 01-11iOs遷至WKWebView跨過的一些坑
- 01-11iOS模擬中獎名單循環(huán)滾動效果
隨機(jī)閱讀
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 01-10C#中split用法實(shí)例總結(jié)
- 01-10delphi制作wav文件的方法
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 04-02jquery與jsp,用jquery
- 01-11ajax實(shí)現(xiàn)頁面的局部加載
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置


