動態(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
? ??