起底 iOS 包瘦身

概述

本文會(huì)從圖片和代碼兩個(gè)維度不撑,來(lái)進(jìn)行包瘦身實(shí)踐。

圖片層面戳葵,可以優(yōu)化的點(diǎn)包括:

  1. 壓縮圖片
  2. 修改圖片格式
  3. 刪除無(wú)用圖片
  4. 刪除重復(fù)圖片

代碼層面红且,介紹查找并刪除 Objective-C 或 Swift 代碼的方法躲株。

1 圖片優(yōu)化

1-1 壓縮圖片

圖片壓縮可分為有損壓損和無(wú)損壓損片部。

  1. 有損壓縮一般是以犧牲圖片的質(zhì)量為代價(jià)來(lái)進(jìn)行的。
  2. 無(wú)損壓縮則通過(guò)去除圖片中的無(wú)用數(shù)據(jù)

比如在 png 格式中霜定,有兩種類型的數(shù)據(jù)塊:

  1. 必要的關(guān)鍵數(shù)據(jù)塊
  2. 非必要的輔助數(shù)據(jù)塊(相機(jī)信息档悠、內(nèi)嵌縮略圖)

png 的無(wú)損壓縮,就是通過(guò)去除非必要的輔助數(shù)據(jù)塊來(lái)實(shí)現(xiàn)的望浩。

這里推薦兩種壓縮方式:

tinypng

tinypng 屬于有損壓縮辖所,壓縮率最高能達(dá)到 70% 以上。提供的是網(wǎng)站壓縮服務(wù)磨德。

ImageOptim

ImageOptim 同時(shí)提供無(wú)損壓縮和有損選項(xiàng)缘回,可以最大程度保證圖片的原始清晰度和細(xì)節(jié)吆视。提供的是 Mac 軟件。

1-2 更改圖片格式(ing)

除了傳統(tǒng)使用的 png 圖片格式酥宴,我們還可以考慮選擇 WebP啦吧。

WebP 是一款由 Google 開(kāi)源的圖片格式,其主要優(yōu)勢(shì)是壓縮率高拙寡。在無(wú)損壓縮模式下授滓,大小比 png 格式少 26%。有損模式模式下倒庵,比 JPEG 圖片小 25-34%褒墨。

通過(guò)工具 cwebp 可以將其它格式的圖片轉(zhuǎn)為 WebP 格式炫刷。

// 通用寫(xiě)法
cwebp [options] input_file -o output_file.webp

// 有損壓縮
cwebp -lossless original.png -o new.webp
// 無(wú)損壓縮
cwebp -lossless original.png -o new.webp

關(guān)于 WebP 在 iOS 中的應(yīng)用擎宝,我自己建了一個(gè)直接可編譯的 demo 工程,供參考浑玛,GitHub 鏈接绍申。

如果想要對(duì) WebP 有更多了解,請(qǐng)參看移動(dòng)端圖片格式調(diào)研顾彰。

1-3 刪除無(wú)用圖片

這里推薦使用 GitHub 上的開(kāi)源庫(kù) LSUnusedResources极阅,查找代碼工程中未使用的資源(包括圖片、mp3涨享、mp4)筋搏。

下載下來(lái)的是一個(gè) Objective-C 的 Mac 工程。運(yùn)行代碼后會(huì)出現(xiàn)下面界面:

LSUnusedResources 界面

允許設(shè)置的參數(shù)包括:

  1. Project Path:工程目錄的路徑
  2. Exclude Folder:設(shè)置工程目錄的查找黑名單(比如 Pods 文件夾下內(nèi)容)
  3. Resource Suffix:想要查找的資源后綴
  4. 正則表達(dá)式設(shè)置:比如在 Objective-C 語(yǔ)言的 .m 文件中查找 @".?"厕隧,Swift 文件里的 ".?"
  5. Ignore similar name:是否忽略名字類似的圖片奔脐,勾選后,即代表在代碼中出現(xiàn) tag_%d吁讨,tag_1.png 即被認(rèn)為使用過(guò)

其源代碼核心代碼如下:

// 將工程文件夾下髓迎,符合該后綴要求的文件全部找出來(lái),并存在一個(gè)字典中
NSDictionary *dic = [[ResourceFileSearcher sharedObject] startWithProjectPath:projectPath excludeFolders:excludeFolders resourceSuffixs:resourceSuffixs];

// 查找源代碼文件中的所有字符串建丧,并存儲(chǔ)到一個(gè)集合中
NSSet *set = [[ResourceStringSearcher sharedObject] startWithProjectPath:projectPath excludeFolders:excludeFolders resourceSuffixs:resourceSuffixs resourcePatterns:[self resourcePatterns]];

NSMutableArray *unusedResult = [NSMutableArray array];
for (NSString *name in resNames) {
    // 如果集合 set 中排龄,則說(shuō)明該資源文件未被使用
    if (![set containsObject:name]) {
      [unusedResult append:dic[name]];
    }
}

// unusedResult 即為所有未使用的資源文件信息

1-4 刪除重復(fù)圖片

重復(fù)圖片的查找問(wèn)題,這里我們轉(zhuǎn)換為比對(duì)不同圖片 MD5 值的問(wèn)題翎朱。
如果兩張圖片數(shù)據(jù)的 MD5 值相同橄维,可以判定為相同圖片。

具體代碼如下:

import os
import sys
import hashlib

# 調(diào)用方式

# python repeat_image.py 目錄路徑

def find_repeat_image(sourceDir):
    result = []
    # 遍歷文件夾
    for dirpath, _, filenames in os.walk(sourceDir):
        md5list = {}
        
        for filename in filenames:
            path = os.path.join(dirpath, filename)

            # 判斷是否是目錄
            if os.path.isdir(path):
                continue
            
            # init md5
            md5obj = hashlib.md5()
            # open rb是讀取二進(jìn)制文件
            fd = open(path, 'rb')

            buff = fd.read()
            md5obj.update(buff)
            fd.close()
            
            # 獲取小寫(xiě) md5 字符串
            filemd5 = str(md5obj.hexdigest()).lower()

            # 檢查該 md5 是否已經(jīng)存在
            if filemd5 in md5list:
                md5list[filemd5].add(path)
            else:
                md5list[filemd5] = set([path])
                
        for key in md5list:
            list = md5list[key]
            # 超過(guò) 1拴曲,則說(shuō)明有存在重復(fù)
            if len(list) > 1:
                result.append(list)

    return result

 # 調(diào)用方式
arr = find_repeat_image(sys.argv[1])
if len(arr) == 0:
    print("無(wú)重復(fù)圖片")
else:
    for repeat in arr:
        print("-----------重復(fù)圖片有------------")
        for item in repeat:
            print(item)

2 代碼優(yōu)化

2-1 LinkMap & Mach-O

注:該方法只適用于 Objective-C

大致思路是從 LinkMap 獲取工程文件中所有類争舞、方法信息,從 Mach-O 找到所有使用過(guò)的類疗韵、方法兑障,兩者的差值即為未使用的代碼。

LinkMap

在 Xcode - Build Settings 中設(shè)置 Write Link Map File 為 YES,Path to Link Map File 設(shè)置為需要輸出的 txt 文件流译。

這里說(shuō)一個(gè)小技巧逞怨,在 $(SRCROOT) 可以代表當(dāng)前工程的根目錄。

具體位置如下圖:

LinkMap 包含三部分:

  1. Object File 包含了.o 目標(biāo)文件和庫(kù)文件
  2. Section 包含了代碼段和數(shù)據(jù)段在 Mach-O 文件的偏移位置以及大小
  3. Symbols 包含了所有的方法福澡、類叠赦、block

我們想要找的方法和類就在 Symbols 中。

Mach-O

Mach-O 文件是 Xcode 編譯成功后的產(chǎn)物革砸,使用 Mach-OView 查看信息除秀。

  1. __objc_selrefs 中列出了所有調(diào)用過(guò)的方法
  2. __objc_classrefs 中列出所有使用過(guò)的的類
  3. __objc_superrefs 中列出所有使用的父類

缺陷

  1. 無(wú)法找到 performSelector 方法調(diào)用的方法
  2. 需要人工進(jìn)行比對(duì)、查找算利,工作量比較大

2-2 運(yùn)行時(shí)檢查類是否被使用過(guò)

注:該方法只適用于 Objective-C

思路:
我們知道在 objc 中類的 initialize 方法執(zhí)行時(shí)機(jī)是在首次向該類發(fā)送消息時(shí)册踩。那么是不是就意味著在 objc 源碼會(huì)記錄是否初始化呢?

答案是有的效拭,在 objc 源碼中暂吉,可以找到以下代碼:

#define RW_INITIALIZED (1<<29)

struct objc_class : objc_object {
    bool isInitialized() {
      return getMeta()->data()->flags & RW_INITIALIZED;
    }
}

因此在運(yùn)行時(shí)盡可能跑完 App 所有場(chǎng)景后,如果某個(gè)類對(duì)象的 isInitialized 屬性仍然返回 NO缎患,則可以判定該類沒(méi)有被使用到慕的。

接下來(lái)遺留的問(wèn)題是,objc 源碼的實(shí)現(xiàn)細(xì)節(jié)是對(duì)開(kāi)發(fā)者屏蔽的挤渔,在代碼工程中如何訪問(wèn)到 isInitialized 屬性呢肮街?

解決辦法是參照 objc 源碼,創(chuàng)建對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)判导。然后進(jìn)行強(qiáng)轉(zhuǎn)訪問(wèn)嫉父。

// 比如在源碼中有一個(gè) objc_class,就仿照創(chuàng)建一個(gè) zyy_objc_class
struct zyy_objc_class : zyy_objc_object {
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
public:
    class_rw_t* data() {
        return bits.data();
    }
    
    zyy_objc_class* metaClass() {
        return (zyy_objc_class *)((long long)isa & ISA_MASK);
    }
};

檢查方法:

+ (BOOL)isObjInitialize {
    zyy_objc_class *cls = (__bridge zyy_objc_class *)([UsedCodeClass class]);

    class_rw_t *metaClassData = cls->metaClass()->data();

    bool isInitialized = (metaClassData->flags) & RW_INITIALIZED;

    return isInitialized;
}

2-3 查找 Swift 中未使用方法和類

推薦一個(gè)三方庫(kù) periphery 可以查找 Swift 中未使用的類和方法骡楼。

不過(guò)要注意的是熔号,因?yàn)?OC 的動(dòng)態(tài)性,所以當(dāng) Swift 代碼暴露給 OC 時(shí)便被認(rèn)定為已使用的代碼鸟整。

periphery 是基于 SourceKit 實(shí)現(xiàn)的引镊,做 Swift 開(kāi)發(fā)的應(yīng)該也會(huì)熟悉另一款基于 SourceKit 實(shí)現(xiàn)的 Swiftlint

SourceKit 提供的功能包括:源代碼轉(zhuǎn)換篮条、語(yǔ)法高亮弟头、排版代碼自動(dòng)補(bǔ)全涉茧、Swift OC 之間頭文件生成等赴恨。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市伴栓,隨后出現(xiàn)的幾起案子伦连,更是在濱河造成了極大的恐慌雨饺,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惑淳,死亡現(xiàn)場(chǎng)離奇詭異额港,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)歧焦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門移斩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人绢馍,你說(shuō)我怎么就攤上這事向瓷。” “怎么了舰涌?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵猖任,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我舵稠,道長(zhǎng)超升,這世上最難降的妖魔是什么入宦? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任哺徊,我火速辦了婚禮,結(jié)果婚禮上乾闰,老公的妹妹穿的比我還像新娘落追。我一直安慰自己,他們只是感情好涯肩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布轿钠。 她就那樣靜靜地躺著,像睡著了一般病苗。 火紅的嫁衣襯著肌膚如雪疗垛。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天硫朦,我揣著相機(jī)與錄音贷腕,去河邊找鬼。 笑死咬展,一個(gè)胖子當(dāng)著我的面吹牛泽裳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播破婆,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼涮总,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了祷舀?” 一聲冷哼從身側(cè)響起瀑梗,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤烹笔,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后抛丽,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體箕宙,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年铺纽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了柬帕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡狡门,死狀恐怖陷寝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情其馏,我是刑警寧澤凤跑,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站叛复,受9級(jí)特大地震影響仔引,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜褐奥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一咖耘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撬码,春花似錦儿倒、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至叫胁,卻和暖如春凰慈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背驼鹅。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工微谓, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人谤民。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓堰酿,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親张足。 傳聞我的和親對(duì)象是個(gè)殘疾皇子触创,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354