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

聊聊iOS 應用瘦身方法思路

移動開發 iOS
如果能夠實現 APP thinning,那么往往 2 倍屏幕的手機包大小會小于 3 倍屏手機的包大小,起到差異性優化的目的。在調研過程中我們還發現,應用的體積與圖片資源的數量密切相關(聽起來好像是廢話)。

[[394887]]

 1. 前言

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

2. iOS 內置資源的集中方式

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

2.1 將圖片存放在 bundle 下

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

但是這種方式有比較明顯的缺點:首先使用 bundle 存儲圖片 iOS 系統不會對其進行壓縮存儲,造成了應用體積的增大。其次是使用 bundle 存儲圖片放棄了 APP thinning,其明顯的表現是使用2倍屏手機的用戶和使用3倍屏手機的用戶下載的應用包大小一樣。

如果能夠實現 APP thinning,那么往往 2 倍屏幕的手機包大小會小于 3 倍屏手機的包大小,起到差異性優化的目的。在調研過程中我們還發現,應用的體積與圖片資源的數量密切相關(聽起來好像是廢話)。

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

為了證實這個觀點特地創建空應用進行測試。首先創建空應用,其大小在 7P 上為 213KB,引入一張 498B 的圖片前后對比如下:

一張498B的圖片

占據 4KB 磁盤空間

未添加資源的應用

添加圖片資源后的大小

上述實驗未經過 App Store 上線認證,僅僅通過本地打包測試,因此觀點僅供參考。

2.2 使用 .ttf 字體文件替代圖標

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

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

使用字體文件會避免這個問題,而且不必導入 @2x 和 @3x 圖片,一套字體文件就能保證 UI 的清晰度。關于如何生成 .ttf 文件在這里就不在贅述了(因為我并不喜歡這個方案),我們只要如何使用就可以了。

字體文件使用起來比較簡單,但是使用方法與 png 圖片的使用方法有很大的不同,因為字體文件時機所展示的圖標都是 UTF8 編碼轉來的字符串。因此當我們需要展示一個圖標的時候不再是使用 UIImageView 了,而是 UILabel。

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

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

使用字體文件的好處總結起來主要有兩點:

  1. 可以降低應用圖片內置資源的體積。
  2. 可以隨意放縮和修改顏色。

但是其缺點也很明顯:

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

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

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

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

APP Slicing

項目優化不能脫離場景,很多很好的方案由于場景的限制并不能起到優化的作用。因此先簡單介紹下我們的項目場景:為了達到跨團隊快速開發的目的,我們項目很早就利用cocoapods 實現組件化。項目中存在多個業務 pod,每個 pod 都有各自的團隊維護,各個團隊的代碼彼此不開放,各個 pod 最終會被編譯為.a的形式。

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

優化的前提之一也是不破壞這種組件化開發的模式,換句話說也就是各個業務線不產生資源耦合、業務線的 RD 不必擔心彼此資源的沖突、業務 Pod 下的資源文件彼此隔離。哪怕招聘團隊中存在 a.png,房產團隊中也存在 a.png 也不會有什么問題。所以我們先要拋出兩個問題:

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

2. 各個 pod 各自維護自己的 Assets.xcassets 會不會造成資源沖突。

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

podspec

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

source_files :源文件路徑。

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

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

實驗發現各個 pod 下都可以創建自己的 xcassets,因此問題1不算問題是問題。如果我們在各個業務 pod 下都創建 .xcassets 文件內置圖片,那么 cocoapods 的腳本會在編譯時將各個目錄下的 xcassets 文件內容提取出來,合并到一個 xcassets 中并生成一個 .car 文件。

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

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

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

mainbundle下存在ImagesBundle

ImageBundle.bundle下存在Assets.car

事情到這里可能已經看到曙光了,但是我們發現通過

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

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

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

才能加載出來。

圖片加載失敗

指定bundle后加載成功

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

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

對此我們提出了一個解決方案:

1. 在 pod 下新建一個空文件夾。找出該 pod 存放圖片的所有 bundle,在新建文件夾下創建與 bundle 數量相等的 Asset。

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

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

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

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

imageWithName:方法內部處理

打包后bundle情況

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

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

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

大家都知道通過XIB加載的視圖都一定會執行 initWithCoder: 方法

UIImageView的子類加載

我們發現在得到執行 [super initWithCoder:aDecoder] 之前通過 lldb 查看 slef.image 是nil。當執行完這行代碼后 self.image 就有值了。

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

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

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

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

aDecoder

hook UINibDecoder的decode方法

打印系統 decode 的所有 key 后發現有個 key 為 UIResourceName,value 為圖片的名稱。也就是說我們能得到 XIB 上設置的圖片名稱了。但是這個圖片的名稱怎么傳遞給這個 XIB 對應的UIImageView 對象呢?

換句話說也就是說我們怎么把圖片傳給這個 XIB 對應的 view 呢?為了將圖片名稱傳給 UIImageView,需要給 aDecoder 添加一個 block 的關聯引用。

UIImageView在initWithCoder:的時候設置回調

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

aDecoder hook到圖片名稱后回調給UIImageView類

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

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

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

重復生成UIImageView對象和aDecoder對照關系

重復生成對象并打印后發現 aDecoder 的地址都相同,也就是說存在一個 aDecoder 對應多個UIImageView 的現象。因此異步方案不適用,需要同步進行設置圖片,因此全局變量最為合適。

其實這一點很容易理解,aDecoder 是與 XIB 對應的,XIB 是不變的所以 aDecoder 是不變的。

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

hook UINibDecoder的decodeObjectForKey

hook UIImageView 的initWithCoder:

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

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

但是我們組內老練的架構師們指出:項目中 hook 如此重要的 API 對增加了項目維護的難度。這也引發了我對項目中 AOP 場景的思考,項目中到底 hook 了多少 API?

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

hook method_exchangeImplementations 方法

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

 

責任編輯:武曉燕 來源: 網羅開發
相關推薦

2015-02-02 11:03:12

2019-01-30 11:21:57

Swift iOS開發

2024-06-27 12:26:32

2019-01-09 13:20:28

GPU虛擬化應用

2021-08-12 18:49:41

DataStreamAPI注冊

2020-12-22 06:05:43

Mbedtls應用基礎

2021-08-23 14:36:26

coredump

2018-05-18 08:20:32

數據治理應用

2021-01-22 05:49:41

數據源思路規劃

2012-08-28 09:12:52

App瘦身

2021-04-30 17:02:52

coredump內核故障

2013-06-28 17:47:59

移動應用

2022-11-26 08:16:26

2022-11-02 09:39:51

數據恢復Kubernetes

2011-09-01 11:12:02

Restaurant 美食應用餐飲應用

2022-01-26 08:47:17

部署應用分布式

2020-06-10 08:55:36

Docker容器工具

2011-04-22 11:09:41

華碩家用臺式電腦晶品CP5

2017-03-02 15:09:29

AndroidAPK瘦身實踐

2017-02-09 17:30:05

Android應用瘦身
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲视频 欧美视频 | 国产精品夜夜春夜夜爽久久电影 | 欧美日韩高清一区二区三区 | 欧美电影在线 | 视频一区二区在线 | 午夜资源| 日日干干夜夜 | 日韩一级免费看 | 97久久精品午夜一区二区 | 久草在线青青草 | 99re在线视频免费观看 | 中文字幕亚洲一区二区va在线 | 91麻豆精品一区二区三区 | 亚洲一区 中文字幕 | 成年人在线视频 | 91精品久久久久久久久久入口 | 婷婷国产一区二区三区 | 91福利网| 国产成人av在线播放 | 亚洲国产一区二区三区四区 | 久久国产亚洲精品 | 精品国产一区二区三区免费 | 亚洲精品一区在线 | 干干干日日日 | 91天堂 | 国内精品久久久久久久影视简单 | 亚洲狠狠爱| 免费黄色a视频 | 五月综合激情在线 | 91精品国产综合久久久久 | 国产精品成人69xxx免费视频 | 美女福利网站 | 精品中文字幕视频 | 国产成人精品一区 | 国产乱码精品1区2区3区 | 欧美激情五月 | 成人免费在线 | 亚洲国产成人精品女人久久久 | 在线国产视频 | 亚洲最新在线视频 | 久久久久久九九九九九九 |