概述
本文會(huì)從圖片和代碼兩個(gè)維度不撑,來(lái)進(jìn)行包瘦身實(shí)踐。
圖片層面戳葵,可以優(yōu)化的點(diǎn)包括:
- 壓縮圖片
- 修改圖片格式
- 刪除無(wú)用圖片
- 刪除重復(fù)圖片
代碼層面红且,介紹查找并刪除 Objective-C 或 Swift 代碼的方法躲株。
1 圖片優(yōu)化
1-1 壓縮圖片
圖片壓縮可分為有損壓損和無(wú)損壓損片部。
- 有損壓縮一般是以犧牲圖片的質(zhì)量為代價(jià)來(lái)進(jìn)行的。
- 無(wú)損壓縮則通過(guò)去除圖片中的無(wú)用數(shù)據(jù)
比如在 png 格式中霜定,有兩種類型的數(shù)據(jù)塊:
- 必要的
關(guān)鍵數(shù)據(jù)塊
- 非必要的
輔助數(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)下面界面:
允許設(shè)置的參數(shù)包括:
- Project Path:工程目錄的路徑
- Exclude Folder:設(shè)置工程目錄的查找黑名單(比如 Pods 文件夾下內(nèi)容)
- Resource Suffix:想要查找的資源后綴
- 正則表達(dá)式設(shè)置:比如在 Objective-C 語(yǔ)言的 .m 文件中查找 @".?"厕隧,Swift 文件里的 ".?"
- 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 包含三部分:
- Object File 包含了.o 目標(biāo)文件和庫(kù)文件
- Section 包含了代碼段和數(shù)據(jù)段在 Mach-O 文件的偏移位置以及大小
- Symbols 包含了所有的方法福澡、類叠赦、block
我們想要找的方法和類就在 Symbols 中。
Mach-O
Mach-O 文件是 Xcode 編譯成功后的產(chǎn)物革砸,使用 Mach-OView 查看信息除秀。
-
__objc_selrefs
中列出了所有調(diào)用過(guò)的方法 -
__objc_classrefs
中列出所有使用過(guò)的的類 -
__objc_superrefs
中列出所有使用的父類
缺陷
- 無(wú)法找到 performSelector 方法調(diào)用的方法
- 需要人工進(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 之間頭文件生成
等赴恨。