Xcode 中添加第三方動態(tài)庫(.dylib)的那些事兒

動態(tài)庫(Unix類似系統(tǒng)中以?.dylib?結(jié)尾的文件)不同于靜態(tài)庫(?.a?結(jié)尾),靜態(tài)庫會在代碼編譯鏈接后打包進(jìn)二進(jìn)制可執(zhí)行程序。而另一種庫是在運行期才會加載,注意這里是加載沒有鏈接的過程。加載意味著程序會在需要的查找該庫。這也是為什么會有一些情況,當(dāng)你很開心的雙擊打開一個程序卻告訴你缺少xx庫的原因。(翻譯注:用Windows同學(xué)很容易碰到缺少?xx.dll?,?dll?文件類似這里的?dylib?庫)。不同的是,靜態(tài)庫會把需要的部分編譯進(jìn)你的app,因此用戶不會遇到這么多麻煩

為什么要使用動態(tài)庫呢?

? ??是因為文件大小! 靜態(tài)庫要比同等功能的動態(tài)庫大很多倍,舉個例子我們比較一個下?OSX?下的?SDL?庫,如果你用?homebrew?安裝了?SDL?它的路徑在?/usr/local/Cellar/sdl2/2.0.3/lib/

? ??

有兩種方式添加一個第三方動態(tài)庫到Xcode?target:

1)安裝庫到系統(tǒng)

2)復(fù)制庫到product

1 安裝庫到系統(tǒng)

? ??對于大多數(shù)的第三方庫可用安裝到系統(tǒng)中,路徑為?/usr/local/lib?或者?/usr/lib?。如果報頭文件找不到需要把頭文件的路徑添加到?Xcode target > Build Setting?。最后在?Xcode target -> Build Phase -> Linked Libraries and Frameworks

? ??但是這種方法并不能方便你的用戶使用app,因為需要用戶自己安裝庫文件。對于app來說沒有比這更糟糕的。

2 復(fù)制?.dylib?到Product

? ??顯而易見一個更好的使用動態(tài)庫的方法應(yīng)該是把?.dylib?與 app 綁定起來。 讓路徑相對于自己比相對于系統(tǒng)要好很多。

? ??程序是如何通知系統(tǒng)需要哪些庫呢?

? ??由于程序是從一個二進(jìn)制文件開始運行,那么這個文件是唯一能夠通知系統(tǒng)需要哪些庫的。從另一個角度來看,這個二進(jìn)制文件是開發(fā)者與用戶系統(tǒng)之間的橋梁,編譯器和鏈接器是建造者。因此我們可以通過?OSX?提供的?otool?問問二進(jìn)制文件。

? ??假設(shè)我有一個叫 Vivi 的項目,APP是?Vivi.app?,Vivi使用了兩個我的庫為?ViviSwiften.framework?和?Viviinterface.framework?,他們也在 Vivi 項目下,盡管你可以雙擊運行APP,但是它本身并不是真正的二進(jìn)制文件。執(zhí)行的二進(jìn)制文件在?AppName.app/Contents/MacOS/AppName?路徑下。

? ??讓我們來問一下這個 Vivi 二進(jìn)制文件

? ??????

看上去?otool?打印出所有鏈接的庫和他們的版本信息

? ??有很多的內(nèi)容,你看,我找到了我自己的?framworks?,?ViviSwiften.framework?和?ViviInterface.framework?。現(xiàn)在我知道了?ViviInterface.framework/Versions/A/ViviInterface?是二進(jìn)制程序的framework路徑(與?.app?不同,?framework?的二進(jìn)制文文件位于?FrameworkName.framework/Versions/A/FrameworkName?),但是?@rpath?的含義是什么?

? ??@excutable_path?@loader_path?和?@rpath?的含義是什么?

參考?OS X Man Page:dyld(1)?(也可以通過?man dyld?),?Build Settings中的變量@rpath,@loader?path,@executable?path.

這三個變量的含義如下:

? ?1) ?@executable_path?總是指向product中的二進(jìn)制執(zhí)行文件路徑,?AppName.app/Contents/MacOS/AppName?.

2) @loader_path?與具體的加載的庫有關(guān),比如?Vivi.app?加載?ViviSwiften.framwork?,那么?@loader_path=/path/to/ViviSwiften.framwork/Versions/A/?(翻譯注:該變量代表加載的庫的二進(jìn)制的路徑,因此不同的庫會解析成不同的路徑,同樣也適用于其他二進(jìn)制的執(zhí)行文件)和?@"executable_path=/path/to/Vivi.app/Contents/MacOS"

3) @rpath?是預(yù)定義的路徑,你可以在?Xcode target > Build Setting > Runpath search Path?進(jìn)行設(shè)置。對于App target 經(jīng)常包含?@executable_path/../Frameworks?, 對于一個 framework target 常用的有?@executable_path/../Frameworks?和?@loader_path/Frameworks?, 對于單元測試一般用?@executable_path/../Frameworks?和?@loader_path/../Frameworks

? ??現(xiàn)在我們知道?Vivi.app?使用的是我自己創(chuàng)建?Viviswiften.framework?和?ViviInterface.framwork?以及一些系統(tǒng)的?framwork?或者?libraries?,但是沒有第三方的動態(tài)庫文件。?Viviswiften.framework?引用了?libSwiften.3.0.dylib?, 讓我們來查看一下

? ??

可以看到?ViviSwiften.framwork?用了一個第三方的動態(tài)庫?libSwiften.dylib?,路徑是?@loader_path/Frameworks/libSwiften.3.0.dylib

系統(tǒng)是如何找到這些庫或者framwork的呢?(以 Vivi 為例)

1) ?用戶雙擊打開 Vivi.app

2)Vivi.app 執(zhí)行?Vivi.app/Contents/MacOS/Vivi

3)查找 Vivi.app 需要的動態(tài)庫

4)找到?@rpath/ViviSwiften.framework/Versions/A/ViviSwiften?, 然后拼成?@executable_path/../Frameworks/..?(翻譯注:原文這里是@executable_path/../Frameworks/ViviSwiften/,應(yīng)該是多了一個ViviSwiften),最后拼成?Vivi.app/Contents/MacOS/Vivi/../Frameworks/ViviSwiften.framework

5)查找 Viviswiften.framwork需要的動態(tài)庫

6)找到?@loader_path/Frameworks/libSwiften.3.0.dylib?,然后拼成?ViviSwiften.framework/Version/A/Frameworks/libSwiften.3.0.dylib

這些信息是如何寫入二進(jìn)制執(zhí)行文件的呢?

? ??我們知道這些信息是在二進(jìn)制文件中,但是他們是如何寫到那里的呢?是兩位辛勤的構(gòu)建者:編譯器和鏈接器。 編譯器會把需要的標(biāo)示符傳遞給鏈接器,鏈接器會負(fù)值查找你告訴它需要的標(biāo)示符,如果你提供的是靜態(tài)庫,鏈接器會把整個文件寫入二進(jìn)制文件中,如果你提供的是動態(tài)庫,只有庫的路徑會寫進(jìn)二進(jìn)制文件。

鏈接器是如何找到這些路徑的呢?

? ??不幸的是,這個路徑并不是APP的開發(fā)者提供的,而是由第三方庫的開發(fā)者提供的。因此你不能夠讓?Xcode?使用相對于你自己的項目或者自己提供路徑。

我們重新看一下?ViviSwiften.framwork?:


我們從第一行可以看到?ViviSwiften?自己的路徑。那就是他的路徑。 描述自己的路徑變量是?install_name?ViviSwiften.framwork?提供了正確的路徑。但是是不是第三方庫都會按我們想的那樣提供合適的路徑呢? 答案是NO。

編譯第三方庫會提供什么樣的路徑呢?

? ??有兩種方式:

1)使用?Homebrew?或者從源碼編譯如(?make install?),庫的路徑會如下:?/usr/lib/libxxx.dylib?或?/usr/local/lib/libxx.dylib .

2)你也可通過?./configure && make?編譯源代碼, 這時候庫的路徑?jīng)]有前綴只有?libxx.dylib

但是這兩種解決方法都不是我們想要的。

把 install_name 更改成需要的

OS X 提供了另一個工具叫?install_name_tool?來改變?install_name?和鏈接庫的?install_name

? ??

最終解決策略

? ? 1)添加庫到?Build Phase?, 添加頭文件搜索路徑,保證沒有編譯和鏈接錯誤。

? ? 2)讓 Xcode 在編譯后復(fù)制?.dylib?文件到product下,添加?New Copy Files Phase?并命名為?Copy Libraries

? ?

修改?Destination?為?Framworks?,添加?.dylib?文件


修改庫的?install_name(如果你導(dǎo)入的是第三方frame庫,有時也不用修改,如果以上步驟后有錯誤就繼續(xù)修改),如果手動導(dǎo)入的第三方.dlib庫,要修改

? ??$ install_name_tool -id @loader_path/Frameworks/libSwiften.3.0.dylib libSwiften.3.0.dylib

? ??

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容