成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

聊聊iOS 應(yīng)用瘦身方法思路

移動(dòng)開發(fā) iOS
如果能夠?qū)崿F(xiàn) APP thinning,那么往往 2 倍屏幕的手機(jī)包大小會(huì)小于 3 倍屏手機(jī)的包大小,起到差異性優(yōu)化的目的。在調(diào)研過程中我們還發(fā)現(xiàn),應(yīng)用的體積與圖片資源的數(shù)量密切相關(guān)(聽起來好像是廢話)。

[[394887]]

 1. 前言

前段時(shí)間注意到我們 APP 的包大小超過100MB了,所以隨口跟呂老板說了下能否采用字體文件(.ttf)替代 PNG 圖片,呂老板對應(yīng)用瘦身很感興趣因此讓我做下技術(shù)調(diào)研。這篇文章主要是將我們的各個(gè)技術(shù)方案的思路做一下整理和總結(jié),希望對大家有所幫助。

2. iOS 內(nèi)置資源的集中方式

在介紹技術(shù)方案前我們先來看下 iOS 內(nèi)置圖片資源都有哪些常見的方式:

2.1 將圖片存放在 bundle 下

這是一種非常常見的方式,項(xiàng)目中各類文件分類放在各個(gè) bundle 下,項(xiàng)目既整潔又能達(dá)到隔離資源的目的。我們項(xiàng)目中圖片絕大多數(shù)都是這樣內(nèi)置的,其加載方式為 [UIImage imageNamed:"xxx.bundle/xxx.png"](請記住這個(gè)字符串的規(guī)則,因?yàn)檫@種規(guī)則非常非常重要!!!"xxx.bundle/xxx.png")。

但是這種方式有比較明顯的缺點(diǎn):首先使用 bundle 存儲(chǔ)圖片 iOS 系統(tǒng)不會(huì)對其進(jìn)行壓縮存儲(chǔ),造成了應(yīng)用體積的增大。其次是使用 bundle 存儲(chǔ)圖片放棄了 APP thinning,其明顯的表現(xiàn)是使用2倍屏手機(jī)的用戶和使用3倍屏手機(jī)的用戶下載的應(yīng)用包大小一樣。

如果能夠?qū)崿F(xiàn) APP thinning,那么往往 2 倍屏幕的手機(jī)包大小會(huì)小于 3 倍屏手機(jī)的包大小,起到差異性優(yōu)化的目的。在調(diào)研過程中我們還發(fā)現(xiàn),應(yīng)用的體積與圖片資源的數(shù)量密切相關(guān)(聽起來好像是廢話)。

換句話說,iPhone 的 rom 存在 4K 對齊的情況,一張 498B 大小的圖片在應(yīng)用包中也要占據(jù) 4KB 大小。因此項(xiàng)目中每添加一張圖片就至少增大了 4KB。

為了證實(shí)這個(gè)觀點(diǎn)特地創(chuàng)建空應(yīng)用進(jìn)行測試。首先創(chuàng)建空應(yīng)用,其大小在 7P 上為 213KB,引入一張 498B 的圖片前后對比如下:

一張498B的圖片

占據(jù) 4KB 磁盤空間

未添加資源的應(yīng)用

添加圖片資源后的大小

上述實(shí)驗(yàn)未經(jīng)過 App Store 上線認(rèn)證,僅僅通過本地打包測試,因此觀點(diǎn)僅供參考。

2.2 使用 .ttf 字體文件替代圖標(biāo)

使用字體文件替代圖片也是一種比較常見的資源內(nèi)置方式。很多應(yīng)用都使用過這種方案,如淘寶、愛奇藝等知名應(yīng)用,都采用過這種方式。

使用字體文件的好處是顯而易見的,如果 APP 中某個(gè)圖片比較大,那么為了保證清晰度,UI 可能會(huì)提供比較大的圖標(biāo)。

使用字體文件會(huì)避免這個(gè)問題,而且不必導(dǎo)入 @2x 和 @3x 圖片,一套字體文件就能保證 UI 的清晰度。關(guān)于如何生成 .ttf 文件在這里就不在贅述了(因?yàn)槲也⒉幌矚g這個(gè)方案),我們只要如何使用就可以了。

字體文件使用起來比較簡單,但是使用方法與 png 圖片的使用方法有很大的不同,因?yàn)樽煮w文件時(shí)機(jī)所展示的圖標(biāo)都是 UTF8 編碼轉(zhuǎn)來的字符串。因此當(dāng)我們需要展示一個(gè)圖標(biāo)的時(shí)候不再是使用 UIImageView 了,而是 UILabel。

字體文件展示圖片的代碼示例

由于我們使用了字體來替代圖片,所以我們可以通過設(shè)置字體的顏色來改變圖標(biāo)的顏色。我們之前經(jīng)常會(huì)遇到一個(gè)場景,如兩個(gè)一模一樣的圖標(biāo)但是由于顏色不同,UI 同學(xué)就需要提供 2 套圖片,每套圖片中包含 @2x 和 @3x 圖片。如果采用了字體替代簡單的圖標(biāo),那么 UI 只需要提供一套字體即可,并且拉伸后也不會(huì)失真。

使用字體文件的好處總結(jié)起來主要有兩點(diǎn):

  1. 可以降低應(yīng)用圖片內(nèi)置資源的體積。
  2. 可以隨意放縮和修改顏色。

但是其缺點(diǎn)也很明顯:

  1. 圖標(biāo)的查找和替換比較麻煩,不如直接使用圖片那樣簡單。
  2. 最重要的是如果在 58 同城 APP 中使用,則意味著無法替換之前存在的圖片,只能起到縮小增量的目的,無法減小全量。

ps:任何一種需要大刀闊斧改革的優(yōu)化都是一種不明智的行為。

2.3圖片存在Assets.xcassets下(蘋果推薦,我也推薦)

使用 Assets.xcassets 是蘋果推薦的一種方式。Assets.xcassets 是 iOS7 推出的一種圖片資源管理工具,將圖片內(nèi)置到 Assets.xcassets 下系統(tǒng)會(huì)對圖片資源進(jìn)行壓縮,并且支持 APP thinning。

APP Slicing

項(xiàng)目優(yōu)化不能脫離場景,很多很好的方案由于場景的限制并不能起到優(yōu)化的作用。因此先簡單介紹下我們的項(xiàng)目場景:為了達(dá)到跨團(tuán)隊(duì)快速開發(fā)的目的,我們項(xiàng)目很早就利用cocoapods 實(shí)現(xiàn)組件化。項(xiàng)目中存在多個(gè)業(yè)務(wù) pod,每個(gè) pod 都有各自的團(tuán)隊(duì)維護(hù),各個(gè)團(tuán)隊(duì)的代碼彼此不開放,各個(gè) pod 最終會(huì)被編譯為.a的形式。

這里需要說明一下我為什么要強(qiáng)調(diào) .a,與 .a 相對應(yīng)的還有一個(gè) .framework,他們之間有一個(gè)重要的區(qū)別就是資源的問題。framework 中可以存放資源,但是 .a 卻不可以,因此生成 .a 的 pod 下的資源會(huì)被轉(zhuǎn)移到 main bundle 下,這為資源沖突造成了隱患,為了避免這種沖突我們之前采用的使用 bundle 管理資源,bundle 名很少會(huì)重復(fù)這樣就大大降低了資源沖突的可能性。

優(yōu)化的前提之一也是不破壞這種組件化開發(fā)的模式,換句話說也就是各個(gè)業(yè)務(wù)線不產(chǎn)生資源耦合、業(yè)務(wù)線的 RD 不必?fù)?dān)心彼此資源的沖突、業(yè)務(wù) Pod 下的資源文件彼此隔離。哪怕招聘團(tuán)隊(duì)中存在 a.png,房產(chǎn)團(tuán)隊(duì)中也存在 a.png 也不會(huì)有什么問題。所以我們先要拋出兩個(gè)問題:

1. cocoapods 是否支持使用 Assets.xcassets。

2. 各個(gè) pod 各自維護(hù)自己的 Assets.xcassets 會(huì)不會(huì)造成資源沖突。

為了弄清楚上面兩個(gè)問題,我們先要看下 podspec 的幾個(gè)重要參數(shù):

podspec

s.public_header_files :表明了哪些路徑下的文件可以在 framework 外被引用。

source_files :源文件路徑。

s.resources :資源文件路徑及文件類型。

s.resource_bundles :資源文件路徑及類型,同時(shí)資源文件會(huì)被打成 bundle。(推薦使用)。

實(shí)驗(yàn)發(fā)現(xiàn)各個(gè) pod 下都可以創(chuàng)建自己的 xcassets,因此問題1不算問題是問題。如果我們在各個(gè)業(yè)務(wù) pod 下都創(chuàng)建 .xcassets 文件內(nèi)置圖片,那么 cocoapods 的腳本會(huì)在編譯時(shí)將各個(gè)目錄下的 xcassets 文件內(nèi)容提取出來,合并到一個(gè) xcassets 中并生成一個(gè) .car 文件。

這樣的話如果資源文件重名,那么很可能其中某一個(gè)文件會(huì)被覆蓋替換。因此我們主要是要解決問題2。查看 podspec 的寫法發(fā)現(xiàn) s.resource_bundles 貌似是我們所需要的法寶。為此我們天真的以為問題馬上就要解決了:

將指定路徑下的資源打包成bundle

最終打包結(jié)果很理想,確實(shí)能夠生成 ImagesBundle.bundle,并且 bundle 下存在Assets.car。

mainbundle下存在ImagesBundle

ImageBundle.bundle下存在Assets.car

事情到這里可能已經(jīng)看到曙光了,但是我們發(fā)現(xiàn)通過

  1. [UIImage imageNamed:@"ImagesBundle.bundle/1"]; 

加載不出來圖片。必須使用

  1. [UIImageimageNamed:@"1"inBundle:[WBIMViewControllericonBundle]compatibleWithTraitCollection:nil]; 

才能加載出來。

圖片加載失敗

指定bundle后加載成功

也就是說只有 Assets.car 如果不在 main bundle 下,那么加載圖片都需要指定bundle。

既然需要指定 bundle 加載圖片,那么如何獲取這個(gè) bundle 呢?換句話說如何才能低成本的將現(xiàn)在項(xiàng)目中的圖片放到特定 bundle下的 Assets.car 文件中呢?

對此我們提出了一個(gè)解決方案:

1. 在 pod 下新建一個(gè)空文件夾。找出該 pod 存放圖片的所有 bundle,在新建文件夾下創(chuàng)建與 bundle 數(shù)量相等的 Asset。

2. 修改 podspec 文件,設(shè)置 resource_bundles 將 Asset 指定為資源,并指定 bundle名稱。如 A.bundle,其對應(yīng)的 Asset 最終資源 bundle 為 A_Asset.bundle。

3. 新增方法,imageWithName:,從符合 xxx.bundle/yyy.png 特征的參數(shù)中獲取 bundle 名和圖片名 xxx_Asset.bundle 和 yyy.png,獲取圖片并返回。

4. 查找并全部替換 imageNamed: 和 imageWithContentOfFile: 為 imageWithName:

只要能拿到原來代碼中 imageNamed: 的參數(shù)就能知道現(xiàn)在圖片存在那個(gè) bundle 下,這樣就能通過 imageNamed:inBundle: 獲取到圖片,其思路如下圖所示:

imageWithName:方法內(nèi)部處理

打包后bundle情況

看到這里老司機(jī)們已經(jīng)應(yīng)該能遇見這種優(yōu)化的成本了。加載圖片都需要指定 bundle 也就意味著成千上萬處的 API 需要修改。我們最初探討到這里的時(shí)候首先想到的是腳本,但是這個(gè)方案很快就被否定了,因?yàn)轫?xiàng)目中存在大量的 XIB,XIB 中設(shè)置圖片我們無法通過腳本替換 API。

為了解決 XIB 設(shè)置圖片的問題,我們首先想到了AOP。通過 hook XIb 加載圖片的方法將方法偷偷替換為 imageNamed:inBundle: ,但是很遺憾我們 hook 了 UIImage 所有加載圖片的方法,沒有一個(gè)方法能拿到 XIB 上所設(shè)置的圖片名稱,也就意味著我們無法得知優(yōu)化后的圖片在哪個(gè) bundle 下,也就不知道圖片該如何加載。

雖然有坎坷,但是我們始終堅(jiān)信 XIB 一定是通過某些方法將圖片加載出來的,我們一定能拿到這個(gè)過程!為了驗(yàn)證這個(gè)問題,首先定義一個(gè) UIImageView 的子類,并將 XIB 上的 UIImageView 指定為這個(gè)子類。

大家都知道通過XIB加載的視圖都一定會(huì)執(zhí)行 initWithCoder: 方法

UIImageView的子類加載

我們發(fā)現(xiàn)在得到執(zhí)行 [super initWithCoder:aDecoder] 之前通過 lldb 查看 slef.image 是nil。當(dāng)執(zhí)行完這行代碼后 self.image 就有值了。

因此推斷圖片的信息(圖片名稱、路徑等信息)都在 aDecoder 中!在網(wǎng)上搜索了一些資料后發(fā)現(xiàn) aDecoder 有一些固定的key,可以通過這些固定的 key 得到一部分信息。如

aDecoder可以通過某些key得到其中信息

很顯然通過 “UIImage” 這個(gè) key 能拿到圖片,但是很遺憾經(jīng)過多次嘗試沒能找到圖片的路徑信息。因此這個(gè)問題的關(guān)鍵是怎么找到合適的 key,為了解決這個(gè)問題,最好是能拿到 aDecoder 的解碼過程。

因此 hook aDecoder 的解碼方法 decodeObjectForKey:是個(gè)不錯(cuò)的選擇。如果能拿到 xib 上設(shè)置的圖片名稱那么我們就可以根據(jù)圖片名稱獲取到正確的圖片路徑。經(jīng)過斷點(diǎn)查看 aDecoder 是UINibDecoder(私有類)類型。

aDecoder

hook UINibDecoder的decode方法

打印系統(tǒng) decode 的所有 key 后發(fā)現(xiàn)有個(gè) key 為 UIResourceName,value 為圖片的名稱。也就是說我們能得到 XIB 上設(shè)置的圖片名稱了。但是這個(gè)圖片的名稱怎么傳遞給這個(gè) XIB 對應(yīng)的UIImageView 對象呢?

換句話說也就是說我們怎么把圖片傳給這個(gè) XIB 對應(yīng)的 view 呢?為了將圖片名稱傳給 UIImageView,需要給 aDecoder 添加一個(gè) block 的關(guān)聯(lián)引用。

UIImageView在initWithCoder:的時(shí)候設(shè)置回調(diào)

在 hook 到的 decodeObjectForKey: 方法中將圖片名稱回傳給 initWithDecoder: 方法:

aDecoder hook到圖片名稱后回調(diào)給UIImageView類

這里需要注意的是一點(diǎn)是:XIB 默認(rèn)設(shè)置圖片是在 rentun value 之后,也就是說如果我們回調(diào)過早有可能圖片被替換為 nil。因此需要 dispatch_after 一下,等 return 之后再回調(diào)圖片名稱并設(shè)置圖片。

受此啟發(fā),我們也可以 hook UIImage 的 imageNamed: 方法,根據(jù)參數(shù)的規(guī)則到 xxxCopy.bundle 下獲取圖片,并返回圖片。這就意味著放棄通過腳本修改 API,減少了代碼的改動(dòng)。

看到這里似乎是沒有什么問題,但是我們忽略了一個(gè)很嚴(yán)重的問題 aDecoder 對象和 UIImageView 類型的對象是一一對應(yīng)的嗎?一個(gè) imageView 它的 aDecoder 是它唯一擁有的嗎?帶著這個(gè)問題,我們先來看下打印信息:

重復(fù)生成UIImageView對象和aDecoder對照關(guān)系

重復(fù)生成對象并打印后發(fā)現(xiàn) aDecoder 的地址都相同,也就是說存在一個(gè) aDecoder 對應(yīng)多個(gè)UIImageView 的現(xiàn)象。因此異步方案不適用,需要同步進(jìn)行設(shè)置圖片,因此全局變量最為合適。

其實(shí)這一點(diǎn)很容易理解,aDecoder 是與 XIB 對應(yīng)的,XIB 是不變的所以 aDecoder 是不變的。

因此異步回調(diào)的方案不適用,需要同步進(jìn)行設(shè)置圖片,在這種情況(主線程串行執(zhí)行)下跨類傳值全局變量最為合適:

hook UINibDecoder的decodeObjectForKey

hook UIImageView 的initWithCoder:

上面兩段代碼僅僅介紹思路,可能加載圖片的代碼并不是十分的嚴(yán)謹(jǐn),請讀者自己鑒別。同理hook 項(xiàng)目中 UIImage 所用到的加載圖片的API即可加載圖片。

如果將所有的hook方法放到一個(gè)類中,那么只要將這個(gè)類拖入到項(xiàng)目中,并將項(xiàng)目中所有的bundle下的圖片都放到對應(yīng)的 Assets.xcassets 文件下那么無需修改一行代碼即可將所有的圖片遷移到 Assets.xcassets 下,達(dá)到應(yīng)用瘦身的目的。

但是我們組內(nèi)老練的架構(gòu)師們指出:項(xiàng)目中 hook 如此重要的 API 對增加了項(xiàng)目維護(hù)的難度。這也引發(fā)了我對項(xiàng)目中 AOP 場景的思考,項(xiàng)目中到底 hook 了多少 API?

可能在我場多年的老司機(jī)們都難以回答了,為此特地趕制了一個(gè)基于 fishhook 的一個(gè) hook 打印工具,檢測和統(tǒng)計(jì)項(xiàng)目中的 AOP 情況。但是缺點(diǎn)是必須調(diào)整編譯順序保證工具類最先被load。

hook method_exchangeImplementations 方法

檢測方法(字典寫入時(shí)不要忘了加鎖)

 

責(zé)任編輯:武曉燕 來源: 網(wǎng)羅開發(fā)
相關(guān)推薦

2015-02-02 11:03:12

2019-01-30 11:21:57

Swift iOS開發(fā)

2024-06-27 12:26:32

2019-01-09 13:20:28

GPU虛擬化應(yīng)用

2021-08-12 18:49:41

DataStreamAPI注冊

2020-12-22 06:05:43

Mbedtls應(yīng)用基礎(chǔ)

2021-08-23 14:36:26

coredump

2012-08-28 09:12:52

App瘦身

2021-04-30 17:02:52

coredump內(nèi)核故障

2021-01-22 05:49:41

數(shù)據(jù)源思路規(guī)劃

2018-05-18 08:20:32

數(shù)據(jù)治理應(yīng)用

2022-11-26 08:16:26

2022-11-02 09:39:51

數(shù)據(jù)恢復(fù)Kubernetes

2013-06-28 17:47:59

移動(dòng)應(yīng)用

2011-09-01 11:12:02

Restaurant 美食應(yīng)用餐飲應(yīng)用

2011-04-22 11:09:41

華碩家用臺(tái)式電腦晶品CP5

2017-03-02 15:09:29

AndroidAPK瘦身實(shí)踐

2017-02-09 17:30:05

Android應(yīng)用瘦身

2017-07-22 15:54:04

iOS組件化路由

2009-02-19 20:36:30

VistavLite副作用
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 国产欧美日韩一区 | 亚洲播放一区 | 福利视频一区 | 精品一区国产 | 色婷婷激情综合 | 夜夜夜夜夜夜曰天天天 | 欧美日韩一区在线 | 日韩欧美国产一区二区三区 | 日韩国产一区二区三区 | h免费观看 | 91久久精品一区二区二区 | 国产一区二区在线视频 | 日韩欧美一区二区三区四区 | 亚洲天堂精品一区 | 亚洲精品电影网在线观看 | 久久久av中文字幕 | 999久久久久久久 | 国产精品视频偷伦精品视频 | 91.xxx.高清在线| 伊色综合久久之综合久久 | 亚洲精品一级 | 亚洲国产欧美精品 | 97福利在线 | 亚洲精品永久免费 | 男人天堂久久 | 免费黄色录像片 | 91麻豆精品一区二区三区 | 天堂一区在线观看 | 精品91久久 | 九九精品影院 | 国产精品爱久久久久久久 | 特黄毛片| 亚洲一区有码 | 亚洲一二三视频 | 最新伦理片 | 成人在线播放网站 | 国产99热在线| 亚洲成人黄色 | 老司机免费视频 | 91偷拍精品一区二区三区 | re久久|