天天看點

iOS瘦身實踐

資源級的瘦身

  • 使用LSUnusedResources删除無用圖檔。注意隻是簡單的删除。需要自己在此确認。
  • 使用WebP代替PNG, 轉換及壓縮工具isparta。
  • WebP的優點:
    1. Webp 壓縮率⾼,⽀持有損與⽆損壓縮
    2. WebP 體積⼤幅減少,⾁眼看不出差異

      缺點:Webp更加消耗性能,較PNG消耗2倍左右的CPU和解碼時間

代碼級的瘦身-linkmap

首先需要了解linkmap是什麼?LinkMap檔案是Xcode産生可執行檔案的同時生成的連結資訊,用來描述可執行檔案的構造成分,包括代碼段(__TEXT)和資料段(__DATA)的分布情況。比如說可執行檔案的構成是怎樣,裡面的内容都是些什麼,

  • 預設xcode在debug下是不生成linkmap檔案的,是以我們首先要設定在debug模式下開啟生成linkmap,在build setting中搜尋link map,設定write link map file為YES,再次運作可以可以在 path to link map file中找到對應生成的text檔案。以為自己的位址舉例:

    /Users/使用者名/Library/Developer/Xcode/DerivedData/項目名稱-hhqqxhkyzdufzbfyuefaytyuxebf/Build/Intermediates.noindex/項目名稱.build/Debug-iphoneos/項目名稱.build/項目名稱-LinkMap-normal-arm64.txt

    其中項目名稱後面的随機字元串可能不一緻。
  • 分析生成的link map中的執行檔案大小。WMLinkMapAnalyzer。可以檢視靜态庫是否過大。如果過大可以參考

    lipo -info libWeChatSDK.a

    Architectures in the fat file: libWeChatSDK.a are: armv7 armv7s i386 x86_64 arm64

    i386,x86_64,是模拟器的指令集,如果不考慮模拟器運作,可以删除。

使用下面腳本或者使用appcode的code inspect 來檢查未使用的類和方法

// 查找無用的類腳本

# 使用方法:python py檔案 Xcode工程檔案目錄

# -*- coding:UTF-8 -*-
import sys
import os
import re

if len(sys.argv) == :
    print '請在.py檔案後面輸入工程路徑' 
    sys.exit()

projectPath = sys.argv[]
print '工程路徑為%s' % projectPath

resourcefile = []
totalClass = set([])
unusedFile = []
pbxprojFile = []

def Getallfile(rootDir): 
    for lists in os.listdir(rootDir): 
        path = os.path.join(rootDir, lists) 
        if os.path.isdir(path): 
            Getallfile(path) 
        else:
            ex = os.path.splitext(path)[]  
            if ex == '.m' or ex == '.mm' or ex == '.h':
                resourcefile.append(path)
            elif ex == '.pbxproj':
                pbxprojFile.append(path)

Getallfile(projectPath)

print '工程中所使用的類清單為:'
for ff in resourcefile:
    print ff

for e in pbxprojFile:
    f = open(e, 'r')
    content = f.read()
    array = re.findall(r'\s+([\w,\+]+\.[h,m]{1,2})\s+',content)
    see = set(array)
    totalClass = totalClass|see
    f.close()

print '工程中所引用的.h與.m及.mm檔案'
for x in totalClass:
    print x
print '--------------------------'

for x in resourcefile:
    ex = os.path.splitext(x)[]
    if ex == '.h': #.h頭檔案可以不用檢查
        continue
    fileName = os.path.split(x)[]
    print fileName
    if fileName not in totalClass:
        unusedFile.append(x)

for x in unusedFile:
    resourcefile.remove(x)

print '未引用到工程的檔案清單為:'

writeFile = []
for unImport in unusedFile:
    ss = '未引用到工程的檔案:%s\n' % unImport
    writeFile.append(ss)
    print unImport

unusedFile = []

allClassDic = {}

for x in resourcefile:
    f = open(x,'r')
    content = f.read()
    array = re.findall(r'@interface\s+([\w,\+]+)\s+:',content)
    for xx in array:
        allClassDic[xx] = x
    f.close()

print '所有類及其路徑:'
for x in allClassDic.keys():
    print x,':',allClassDic[x]

def checkClass(path,className):
    f = open(path,'r')
    content = f.read()
    if os.path.splitext(path)[] == '.h':
        match = re.search(r':\s+(%s)\s+' % className,content)
    else:
        match = re.search(r'(%s)\s+\w+' % className,content)
    f.close()
    if match:
        return True

ivanyuan = 
totalIvanyuan = len(allClassDic.keys())

for key in allClassDic.keys():
    path = allClassDic[key]

    index = resourcefile.index(path)
    count = len(resourcefile)

    used = False

    offset = 
    ivanyuan += 
    print '完成',ivanyuan,'共:',totalIvanyuan,'path:%s'%path


    while index+offset < count or index-offset > :
        if index+offset < count:
            subPath = resourcefile[index+offset]
            if checkClass(subPath,key):
                used = True
                break
        if index - offset > :
            subPath = resourcefile[index-offset]
            if checkClass(subPath,key):
                used = True
                break
        offset += 

    if not used:
        str = '未使用的類:%s 檔案路徑:%s\n' %(key,path)
        unusedFile.append(str)
        writeFile.append(str)

for p in unusedFile:
    print '未使用的類:%s' % p

filePath = os.path.split(projectPath)[]
writePath = '%s/未使用的類.txt' % filePath
f = open(writePath,'w+')
f.writelines(writeFile)
f.close()
           

以上查找無用類的方法隻能作為參考。還需要人工篩選一遍。很多通過xib建立、或者cell通過register方法建立的。腳本是無法區分的。是以還需人工審查一遍。但也已經大大減少了很多繁瑣的工作量了。

編譯器級别瘦身。

  • 這個最有用的一個選項是Deployment Postprocessing / Strip Linked Product / Symbols Hidden by Default 在release版本應該設為yes

更多的瘦身方法

參考滴滴出行iOS端瘦身實踐

個人總結:項目嘗試過上述幾種方法,Xcode最終打出ipa包由原來的63M縮小到了50M。因為項目原因,個人感受這邊最為行之有效的應該算是資源的壓縮。可能根據不同項目的場景各有不同。大家都可以進行嘗試!

參考:

滴滴出行iOS端瘦身實踐

當我們談論iOS瘦身的時候,我們到底在談論些什麼

繼續閱讀