轉(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中可以使用圖形界面很方便地管理資源文件字币,如下圖所示:
一般大家會在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”排龄,如下圖所示:
簡單介紹下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ā)成本儿倒。