你不了解的Assets.car

轉(zhuǎn)發(fā):http://www.reibang.com/p/03c001cfa954

.car文件是蘋果.xcassets文件夾中的資源編譯后生成的,會以Assets.car的名稱打包進應(yīng)用的安裝包中约计。這篇文章中我們將分析car文件的文件結(jié)構(gòu)字管,并討論如何將car文件中的顏色可岂、圖片吏夯、pdf猎提、文檔等資源解析出來虏等。

原創(chuàng)文章只怎,如需轉(zhuǎn)載請在下面留言讓我知道??。不留言不在開頭標(biāo)明出處鏈接的壞同學(xué)墨叛,1字1元索賠??


背景.

方案引入

公司中現(xiàn)有換膚機制要花費大量時間對軟件進行改造止毕,具體方案就不介紹了,總之大家都吐槽不好用??漠趁。為了能盡量貼近蘋果官方推薦的資源管理方式扁凛、減少開發(fā)者學(xué)習(xí)成本、使用官方的優(yōu)化方案闯传,最開始的出發(fā)點是要使用Asset Catalogs管理資源谨朝。Asset Catalogs管理方式在Xcode工程中最常接觸到的就是名為“Assets.xcassets”的文件夾,創(chuàng)建工程時默認(rèn)就會創(chuàng)建這個文件夾甥绿,在Xcode中可以使用圖形界面很方便地管理資源文件字币,如下圖所示:

image

一般大家會在xcassets中放置應(yīng)用的圖標(biāo)、UI切圖共缕,從iOS 11開始洗出,xcassets中還可以放置顏色信息。其實還可以在其中放置PDF图谷、紋理翩活、Data等數(shù)據(jù),只是平時很少用到便贵,而且換膚需求也不要求涉及這些數(shù)據(jù)菠镇,所以我們只要關(guān)心切圖和顏色就可以了承璃。如果能夠把顏色利耍、UI切圖、圖標(biāo)這些資源在編譯時動態(tài)替換為新的資源程癌,即可滿足現(xiàn)在行業(yè)的需求税稼。幸運的是扰肌,xcassets中的資源不僅能在編譯時替換晶府,甚至可以在運行時尸曼,通過從不同的bundle中讀取資源達到動態(tài)換膚的目的躲株,這大大增加了這種方案的可擴展性。

遇到的問題

眾所周知档悠,xcassets中的資源會被編譯為car格式的文件廊鸥,保存于App或framework的包中。編譯過程中會做圖片壓縮等優(yōu)化工作辖所,雖然這些是我們想要的惰说,但同時也產(chǎn)生一個問題,那就是必須通過系統(tǒng)API才能讀取出car中的資源缘回。讀取圖片不是什么難事吆视,但從下面UIKit中的代碼片段可以看出,讀取顏色信息的API只有在iOS11之后才能使用酥宴。

@interface UIColor (UIColorNamedColors)
+ (nullable UIColor *)colorNamed:(NSString *)name API_AVAILABLE(ios(11.0));      // load from main bundle
+ (nullable UIColor *)colorNamed:(NSString *)name inBundle:(nullable NSBundle *)bundle compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection API_AVAILABLE(ios(11.0));
@end

而我們的應(yīng)用至少要兼容3個最新版本的iOS系統(tǒng)啦吧,目前最新系統(tǒng)是iOS 12,也就是說從iOS 10的系統(tǒng)沒法讀取出顏色信息拙寡。為了解決這個問題授滓,我們需要自己實現(xiàn)讀取car文件中顏色信息的邏輯,而關(guān)于car文件的格式肆糕,蘋果是沒有公開說明文檔的般堆,這就是我們要攻克的最大的難題。

解析car文件

car究竟是什么

為了避免重復(fù)造輪子诚啃,最先想到的方案就是使用第三方的框架實現(xiàn)解析功能淮摔。但是過程不那么順利,Github上開源工具有很多绍申,但全都是在macOS中解析car文件噩咪,而且沒有一個是真正解析car的,都是通過iOS或者macOS系統(tǒng)提供的庫實現(xiàn)的极阅。如果能用系統(tǒng)庫胃碾,我們也就不用解析car了。
之后在公司內(nèi)部找了一些比較資深的iOS開發(fā)者筋搏,咨詢了一圈仆百,發(fā)現(xiàn)也沒有人做過這個事情。百度也沒有找到相關(guān)的內(nèi)容奔脐,一度差點否決了這個方案俄周。最終經(jīng)過大量查找,在Wikipedia中發(fā)現(xiàn)了蛛絲馬跡髓迎,car文件的結(jié)構(gòu)是BOM峦朗!馬上用"Synalyze It! Pro"應(yīng)用分析一下car的內(nèi)容,發(fā)現(xiàn)前8個字節(jié)真的是“BOMStore”排龄,如下圖所示:

image

簡單介紹下BOM文件格式波势,BOM是“Bill of Materials”的縮寫。之前被用于macOS的應(yīng)用安裝器中,用于標(biāo)識哪些文件需要安裝尺铣、哪些需要移除或者升級拴曲。具體介紹可以參考這個鏈接https://en.wikipedia.org/wiki/BOM_(file_format)
幸好BOM文件已經(jīng)在macOS中用了很多年,雖然官方?jīng)]有文檔凛忿,但內(nèi)部結(jié)構(gòu)有人嘗試逆向過澈灼。BOM只定義了一種存儲信息的樹狀結(jié)構(gòu),并沒有規(guī)定樹中存儲的數(shù)據(jù)是什么樣的店溢。分析出BOM數(shù)據(jù)之后叁熔,還要分析出顏色信息是以什么格式儲存在BOM結(jié)構(gòu)中的,這篇文章Reverse engineering the .car file format (compiled Asset Catalogs)介紹了car中的信息床牧,但使用了macOS中的BOM.framework解析BOM中的數(shù)據(jù)者疤,iOS中并沒有這個框架。我們要結(jié)合上面的文章和之前開發(fā)者分析出的BOM大致結(jié)構(gòu)叠赦,解析出car中指定名稱的顏色數(shù)據(jù)。

BOM結(jié)構(gòu)

感謝PureDarwin在Github上的開源項目osxbom革砸。雖然這個項目是為了解析macOS的應(yīng)用安裝器中的數(shù)據(jù)除秀,但是BOM的頭、樹中的索引和節(jié)點等數(shù)據(jù)結(jié)構(gòu)和解析方法都很有幫助算利。下面大致介紹一下BOM結(jié)構(gòu)册踩,給大家一些啟發(fā)。
BOM文件的最開頭效拭,是頭數(shù)據(jù)暂吉,相信大家看一下osxbom中的結(jié)構(gòu)體就明白了:

struct BOMHeader {
  char magic[8]; // = BOMStore
  uint32_t unknown0; // = 1?
  uint32_t unknown1; // = 73 = 0x49?
  uint32_t indexOffset; // Length of first part
  uint32_t indexLength; // Length of second part
  uint32_t varsOffset;
  uint32_t trailerLen; // FIXME: What does this data at the end mean?
} __attribute__((packed));

  • indexOffset
    它的含義是索引表在BOMHeader后面多少個字節(jié)地址偏移處。這里有一個新概念就是索引表缎患,索引表可以根據(jù)一個(索引)數(shù)字找到BOM文件中對應(yīng)的地址偏移慕的。有了索引表,只要給出一個很小的(索引)數(shù)字挤渔,就可以跳轉(zhuǎn)到BOM中的任意位置讀取數(shù)據(jù)肮街。索引表的結(jié)構(gòu)比較簡單,看下面的結(jié)構(gòu)體就可以理解了:
struct BOMIndex {
  uint32_t address;
  uint32_t length;
} __attribute__((packed));

struct BOMIndexHeader {
  uint32_t unknown0; // FIXME: What is this? It is not the length of the array...
  struct BOMIndex index[FLEXIBLE_ARRAY_MEMBER];
} __attribute__((packed));

索引數(shù)字如果是3判导,就找到index[3]結(jié)構(gòu)體嫉父,其中就存儲著地址偏移和數(shù)據(jù)塊的長度。

  • varsOffset
    BOMHeader中還有個重要的信息是varsOffset眼刃,它表示BOM中的每一棵樹的名稱绕辖、數(shù)據(jù)位置的索引數(shù)字的表,在BOMHeader后面多少個字節(jié)偏移處擂红,結(jié)構(gòu)如下所示:
struct BOMVar {
  uint32_t index;
  uint8_t length;
  char name[FLEXIBLE_ARRAY_MEMBER]; // length
} __attribute__((packed));

struct BOMVars {
  uint32_t count; // Number of entries that follow
  struct BOMVar first[FLEXIBLE_ARRAY_MEMBER];
} __attribute__((packed));

如果我們要找某一棵名為RENDITIONS的樹仪际,只要遍歷BOMVars中的所有BOMVar,判斷名稱是否和我們要的一致,如果一致則根據(jù)BOMVar中的index索引表中查找到對應(yīng)的地址偏移即可取出這棵樹的數(shù)據(jù)弟头。

  • 其他
    上面已經(jīng)介紹了BOM中的主要內(nèi)容吩抓,關(guān)系有點繞,但是也很精妙赴恨,可以體會一下設(shè)計BOM結(jié)構(gòu)的人思維方式疹娶。總之伦连,有了上面的基礎(chǔ)知識雨饺,就可以根據(jù)樹的名稱拿到具體的數(shù)據(jù)塊了。其實每棵樹的數(shù)據(jù)塊的結(jié)構(gòu)設(shè)計惑淳,也是相當(dāng)巧妙的额港,有興趣的同事可以看下osxbom源碼自行分析,這里由于篇幅原因不再贅述歧焦。

car信息

上面已經(jīng)提到移斩,car數(shù)據(jù)的結(jié)構(gòu)在Reverse engineering the .car file format (compiled Asset Catalogs)博客中已經(jīng)有比較詳細(xì)的描述。
幾乎所有圖片绢馍、顏色等信息都存儲在名為RENDITIONS的樹中向瓷,樹中每個節(jié)點的數(shù)據(jù)都是下面這個結(jié)構(gòu)所示的結(jié)構(gòu):

struct csiheader {
    uint32_t tag;                               // 'CTSI'
    uint32_t version;
    struct renditionFlags renditionFlags;
    uint32_t width;
    uint32_t height;
    uint32_t scaleFactor;
    uint32_t pixelFormat;
    struct {
        uint32_t colorSpaceID:4;
        uint32_t reserved:28;
    } colorSpace;
    struct csimetadata csimetadata;
    struct csibitmaplist csibitmaplist;
} __attribute__((packed));

看結(jié)構(gòu)體已經(jīng)非常明確了,csimetadata中存儲著當(dāng)前節(jié)點數(shù)據(jù)的類型(比如圖片舰涌、顏色猖任、PDF),還有數(shù)據(jù)的名稱(比如圖片名瓷耙、顏色名朱躺、PDF文件名)。遍歷樹中每個節(jié)點搁痛,找到希望獲取的顏色類型節(jié)點长搀,并且顏色名和希望獲取的一致,剩下的就是去除顏色數(shù)據(jù)即可落追。其他類型的數(shù)據(jù)在博客中也都提到盈滴,有興趣的同事可以自己研究一下,下面我就以解析顏色數(shù)據(jù)為例轿钠。
通過csiheader里面csibitmaplist中的數(shù)據(jù)偏移等信息巢钓,可以找到顏色信息存儲的具體位置(是的,這個名字看起來很像圖片疗垛,因為早期car中只能存儲圖片)症汹。
到這里我們就獲取到了顏色信息的數(shù)據(jù),它的結(jié)構(gòu)如下所示:

struct csicolor {
    uint32_t tag;                   // COLR
    uint32_t version;
    struct {
        uint32_t colorSpaceID:8;
        uint32_t unknown0:3;
        uint32_t reserved:21;
    } colorSpace;
    uint32_t numberOfComponents;
    double components[];
} __attribute__((packed));

上面的結(jié)構(gòu)體名稱是csicolor贷腕,因為這是顏色信息的結(jié)構(gòu)體背镇,其他類型數(shù)據(jù)有對應(yīng)的結(jié)構(gòu)體咬展。我們可以看到,其中有顏色空間colorSpaceID,它表示顏色使用的SRGB還是灰度等等瞒斩。組件數(shù)量numberOfComponents和組件components破婆,表示的是某種顏色空間中的不同組件,比如SRGB中的紅胸囱、綠祷舀、藍(lán)、透明通道的亮度值烹笔,或者灰度顏色中的亮度裳扯、透明度值。

總結(jié)

至此谤职,我們就把car文件中的顏色信息全部讀取出來了饰豺。篇幅有限,有很多細(xì)節(jié)沒有羅列允蜈。這其中也確實有大量的工作要做冤吨,我們要分析、驗證BOM結(jié)構(gòu)解析是否正確饶套,驗證car信息的正確性和兼容性锅很。這可能需要用到"Synalyze It! Pro",還需要查閱大量資料凤跑、做大量實驗、使用不同版本的Xcode編譯出car驗證我們的解析正確性叛复。
最終仔引,我們創(chuàng)造性地實現(xiàn)了在iOS平臺上對car文件中所有的圖片、顏色褐奥、文檔等資源和其他附加信息的讀取咖耘,此前國內(nèi)外都沒有公開資料顯示有哪個團隊實現(xiàn)過這個完整的過程(當(dāng)然除了蘋果??)。新的換膚方案配合框架的讀取資源API撬码,節(jié)省了大量開發(fā)成本儿倒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市呜笑,隨后出現(xiàn)的幾起案子夫否,更是在濱河造成了極大的恐慌,老刑警劉巖叫胁,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凰慈,死亡現(xiàn)場離奇詭異,居然都是意外死亡驼鹅,警方通過查閱死者的電腦和手機微谓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門森篷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人豺型,你說我怎么就攤上這事仲智。” “怎么了姻氨?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵钓辆,是天一觀的道長。 經(jīng)常有香客問我哼绑,道長岩馍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任抖韩,我火速辦了婚禮蛀恩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘茂浮。我一直安慰自己双谆,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布席揽。 她就那樣靜靜地躺著顽馋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪幌羞。 梳的紋絲不亂的頭發(fā)上寸谜,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音属桦,去河邊找鬼熊痴。 笑死,一個胖子當(dāng)著我的面吹牛聂宾,可吹牛的內(nèi)容都是我干的果善。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼系谐,長吁一口氣:“原來是場噩夢啊……” “哼巾陕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起纪他,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤鄙煤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后茶袒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體馆类,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年弹谁,在試婚紗的時候發(fā)現(xiàn)自己被綠了乾巧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片句喜。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖沟于,靈堂內(nèi)的尸體忽然破棺而出咳胃,到底是詐尸還是另有隱情,我是刑警寧澤旷太,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布展懈,位于F島的核電站,受9級特大地震影響供璧,放射性物質(zhì)發(fā)生泄漏存崖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一睡毒、第九天 我趴在偏房一處隱蔽的房頂上張望来惧。 院中可真熱鬧,春花似錦演顾、人聲如沸供搀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽葛虐。三九已至,卻和暖如春棉钧,著一層夾襖步出監(jiān)牢的瞬間屿脐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工宪卿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留摄悯,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓愧捕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親申钩。 傳聞我的和親對象是個殘疾皇子次绘,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344