一、動态連結庫與靜态連結庫的差別
庫從本質上來說是一種可執行代碼的二進制格式,可以被載入記憶體中執行。庫分靜态庫和動态庫兩種。
1. 靜态函數庫
這類庫的名字一般是libxxx.a;利用靜态函數庫編譯成的檔案比較大,因為整個 函數庫的所有資料都會被整合進目标代碼中,他的優點就顯而易見了,即編譯後的執行程式不需要外部的函數庫支援,因為所有使用的函數都已經被編譯進去了。當然這也會成為他的缺點,因為如果靜态函數庫改變了,那麼你的程式必須重新編譯。
2. 動态函數庫
這類庫的名字一般是libxxx.so;相對于靜态函數庫,動态函數庫在編譯的時候 并沒有被編譯進目标代碼中,你的程式執行到相關函數時才調用該函數庫裡的相應函數,是以動态函數庫所産生的可執行檔案比較小。由于函數庫沒有被整合進你的程式,而是程式運作時動态的申請并調用,是以程式的運作環境中必須提供相應的庫。動态函數庫的改變并不影響你的程式,是以動态函數庫的更新比較友善。
二、使用庫的優勢
使用靜态庫的好處
1,子產品化,分工合作
2,避免少量改動經常導緻大量的重複編譯連接配接
3,也可以重用,注意不是共享使用
動态庫使用有如下好處:
1使用動态庫,可以将最終可執行檔案體積縮小
2使用動态庫,多個應用程式共享記憶體中得同一份庫檔案,節省資源
3使用動态庫,可以不重新編譯連接配接可執行程式的前提下,更新動态庫檔案達到更新應用程式的目的。
三、iPhone對于連結庫的支援
iPhone官方隻支援靜态庫聯編,不支援動态連結庫。而對于靜态庫有兩種方式:一種是生成.a檔案;一種是生成靜态framework。所謂的靜态framework,實際上也是一種靜态庫,其相對于.a檔案來說,差別在于其不僅包含了庫檔案,還包含了頭檔案和資源檔案。而在使用.a檔案時,還要另外添加.h檔案。
iPhone不支援動态連結庫的原因可能有:
①共享給誰?(因為在現在的iPhone,iPodTouch,iPad上面程式都是單程序的,也就是某一時刻隻有一個程序在運作,你使用的時候隻有你一個應用程式存在,其他的應該被挂起了,即便是可以同時多個程序運作,别人能使用你的共享庫裡的東西嗎?你這個是給你自己的程式定制的。)
②目前蘋果的AppStore不支援子產品更新,無法更新某個單獨檔案(除非自己寫一個更新機制:有自己的服務端放置最新動态庫檔案),也就是說,就算你通過其他方式産生了動态連結庫,也無法達到更新動态庫檔案進而更新應用程式的目的。
四、Xcode對于建立庫的支援
Xcode不支援自己建立動态連結庫。而framework也分為靜态架構和動态架構,大部分架構都是動态連結庫的形式。因為隻有蘋果才能在iOS裝置上安裝動态庫,是以我們也無法建立動态架構。
而且蘋果也從Xcode中移除了建立靜态iOS架構的功能!!!
通常來說,Xcode隻支援建立靜态庫檔案.a。但是實際上Appstore是允許代碼封裝在靜态連結庫中的。是以,出現了所謂的”僞架構”和”真架構”。
五、架構的類别和使用
前面說了,架構分為靜态架構和動态架構,對于靜态架構,在生成過程中又有了”真”、”僞”的差別。
1、”僞”架構
“僞”架構是破解的“reloacatable object file”(可重定位格式的目标檔案, 儲存着代碼和資料,适合于和其他的目标檔案連接配接到一起,用來建立一個可執行目标檔案或者是一個可共享目标檔案),它可以讓Xcode編譯出類似架構的東西——其本質是一個bundle。
“僞架構”模闆把整個過程分為幾個步驟,用某些腳本去産生一個真正的靜态架構(基于靜态庫而不是reloacatable object file)。而且,架構項目還是把它定義為wrapper.cfbundle類型,一種Xcode中的“二等公民”。
是以它跟“真”靜态架構一樣可以正常工作,但當存在依賴關系時就有麻煩了。
依賴問題
如果不使用依賴,隻是建立普通的項目是沒有任何問題的。但是如果使用了項目依賴(比如在workspace中),Xcode就悲劇了。當你點選“Link Binary With Libraries”下方的’+’按鈕時,“僞架構”無法顯示在清單中。你可以從你的“僞”架構項目的Products下面将它手動拖入,但當你編輯你的主項目時,會出現警告:
warning: skipping file '/somewhere/MyFramework.framework' (unexpectedfile type 'wrapper.cfbundle' in Frameworks & Libraries build phase)
并伴随“僞”架構中的連結錯誤。
幸運的是,有個辦法來解決它。你可以在”Other Linker Flags”中用”-framwork”開關手動告訴linker去使用你的架構進行連結:
-framework MyFramework
警告仍然存在,但起碼能正确連結了。
2、”真”架構
“真”架構各個方面都符合“真”的标準。它是真正的靜态架構,正如使用蘋果在從Xcode中去除的那個功能所建立的一樣。
為了能建立真正的靜态架構項目,你必需在Xcode中安裝一個xcspec檔案。
如果你釋出一個“真”架構項目(而不是編譯),希望去編譯這個架構的人必需也安裝xcspec檔案(使用本次釋出的安裝腳本),以便Xcode能了解目标類型。
注意:如果你正在釋出完全編譯的架構,而不是架構項目,最終使用者并不需要安裝任何東西
3、不同架構類型的使用時機
簡單來說,你可以這樣決定用哪一種架構:
· 如果你不想修改Xcode,那麼請使用“僞”架構版本
· 如果你隻是想共享二進制(不是項目),兩種都可以
· 如果你想把架構共享給不想修改Xcode的開發者,使用“僞”架構版本
· 如果你想把架構共享給修改過Xcode的開發者,使用“真”架構版本
· 如果你想把架構項目作為另一個項目的依賴(通過workspace或者子項目的方式),請使用“真”架構(或者“僞”架構,使用-framework——見後)
· 如果你想在你的架構項目中加入其他靜态庫/架構,并把它們也連結到最終結果以便不需要單獨添加到使用者項目中,使用“僞”架構
六、IOS靜态架構的建立、編譯和使用
1、安裝架構模闆
要想在xcode中建立靜态架構,須得先安裝相應的模闆iOS Universal Framework Mk
iOS Universal Framework Mk 8的下載下傳位址:https://github.com/kstenerud/iOS-Universal-Framework
解壓下載下傳檔案,打開Terminal,進入到剛下載下傳檔案,Fake Framework下面。輸入指令:sh install.sh。同理安裝Real Framework下的檔案。安裝完畢如下圖:
分别運作Real Framework目錄或Fake Framework目錄下的install.sh腳本進行安裝(或者兩個你都運作)。
重新開機Xcode,你将在新項目向導的Framework&Library下看到StaticiOS Framework(或者Fake Static iOS Framework)。
若要解除安裝這兩個模闆,請運作unistall.sh腳本并重新開機Xcode。
2、 建立一個IOS架構項目
①建立新項目。
②項目類型選擇Framework&Library下的Static iOS Framework(或者Fake Static iOS Framework)。
③ 選擇“包含單元測試”(可選的)。
④在target中加入類、資源等。
⑤凡是其他項目要使用的頭檔案,必需聲明為public。進入target的Build Phases頁,展開Copy Headers項,把需要public的頭檔案從Project或Private部分拖拽到Public部分。
3、 編譯IOS架構、
① 選擇指定target的scheme。
②修改scheme的Run配置(可選)。Run配置預設使用Debug,但在準備部署的時候你可能想使用Release。
③編譯架構(無論目标為iOS device和Simulator都會編譯出相同的二進制,是以選誰都無所謂了)。
④從Products下選中你的framework,“show in Finder”。
在build目錄下有兩個檔案夾:(yourframework).framework and (your framework).embeddedframework.
如果你的架構隻有代碼,沒有資源(比如圖檔、腳本、xib、coredata的momd檔案等),你可以把(yourframework).framework 分發給你的使用者就行了。如果還包含有資源,你必需分發(your framework).embeddedframework給你的使用者。
為什麼需要embedded framework?因為Xcode不會查找靜态架構中的資源,如果你分發(your framework).framework, 則架構中的所有資源都不會顯示,也不可用。
一個embedded framework隻是一個framework之外的附加的包,包括了這個架構的所有資源的符号連結。這樣做的目的是讓Xcode能夠找到這些資源
4、 使用靜态架構
iOS架構和正常的Mac OS動态架構差不多,隻是它是靜态連結的而已。
在你的項目中使用一個架構,隻需把它拖僅你的項目中。在包含頭檔案時,記住使用尖括号而不是雙引号包覆架構名稱。例如,對于架構MyFramework:
#import <MyFramework/MyClass.h>
七、靜态庫.a檔案的編寫
IOS産生.a的靜态庫,比起.framework相對簡單了好些。
下面介紹一下具體生成步驟:
1、建立一個framework&library庫。IOS 下的cocoa touch static library。然後輸入product name 為libsql
2、把libsql.h和libsql.m删除。導入ocsqlite.h和ocsqlite.c(檔案見http://blog.csdn.net/fengsh998/article/details/8278978)
3、修改scheme,設為release版本。
OK,選譯ios device編譯運作。成功後将在目錄的build/products/release-iphoneos/下産生一個liblibsql.a檔案。
注,這裡産生的是真機使用的.a檔案。
選譯iphonesimulator 進行編譯一次,同樣會在build/products/release-iphonesimulator/下産生一個liblibsql.a檔案。
這裡是虛拟機使用的.a檔案。
下面來看一下這兩個檔案有什麼不同之處,使用lipo -info指令。
打開終端。
進入到相應的目錄。
真機的:liblibsql.a檔案資訊。
input file liblibsql.a is not a fat file
Non-fat file: liblibsql.a is architecture: armv7
如圖:
模拟器的:liblibsql.a檔案資訊。
input file liblibsql.a is not a fat file
Non-fat file: liblibsql.a is architecture: i386
如圖:
如果使用真機和模拟器通用,則需要将這兩個檔案合并,使用指令lipo -create xxxx/liblibsql.a xxxxx/liblibsql.a -output libsql.a
同樣可以使用lipo -info 來檢視這個合并的libsql.a
可以看到architectures in the fat file: libsql.a are: i386 armv7
如圖:
八、iOS中建立,使用動态庫(dylib)
由于蘋果不支援自己建立動态庫,是以這裡需要替換兩個檔案
①iOS Device 需要替換的檔案
替換路徑:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Specifications/
②iOS 模拟器需要替換的檔案
替換路徑:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications
具體可參照:http://blog.iosplace.com/?p=33 或者 http://www.cocoachina.com/bbs/simple/?t129814.html
替換完成後重新開機Xcode。
建立動态庫
1:打開Xcode,建立項目,選擇OS X --> Cocoa Library -->輸入動态庫的相關資訊
2:這樣,動态庫已經建立好,但是由于此項目是基于Mac OS X建立的,是以這裡要将project的相關設定作修改
1:Base 設定成 SDK iOS6.0
2:Architectures 設定成 standrand (armv7 armv7s)
3:Installation Directory 設定成 @executable_path/
4:Mach-O Type 設定成 Dynamic Library
5:Executable Prefix 設定成 空
6:打開項目的project.pbxproj(文本編輯器打開)檔案,在編輯器中将producttype 的值修改為 com.apple.product-type.library.dynamic
7:選擇合适的證書檔案
3:将debug改為no
4:在動态庫的相關類中添加一些測試方法
5:删除多餘的framework檔案。值添加Foundation.framework檔案
Xcode--->Preferences--->Locations--->Advanced---->
這樣基本就可以編譯dylib了,但是這裡還有一個需要注意的地方。
編譯證書的選擇
編譯分為裝置編譯(iOS Device)及模拟器編譯(iPad/iPhone Simulator)
在選擇裝置編譯的時候,一定要選擇某個有效的開發者證書。否則編譯會出錯。
相反,選擇模拟器編譯的時候,不需要選擇證書(如果選了證書,也會報錯)。
如果一切正常,那麼在編譯後的檔案會出現在項目檔案夾的Build/Products/.…檔案夾中
參考文檔:1、http://blog.csdn.net/stackhero/article/details/9032999關于靜态庫、動态庫的差別彙總
2、http://blog.csdn.net/stackhero/article/details/9032891 iOS中建立,使用動态庫(dylib)
3、 http://blog.csdn.net/fengsh998/article/details/8291965 IOS 4.2 編寫通用的靜态庫.a檔案
基本知識
在實際的程式設計過程中,通常會把一些公用函數制成函數庫,供其它程式使用,一則提搞了代碼的複用;二則提搞了核心技術的保密程度。
Library使用的兩種方式:封裝lib.a和直接引用lib工程。
一、封裝.a檔案
直接封裝lib.a,向使用者提供頭檔案清單。使用者引用頭檔案并且使用其中方法,但是看不到實作檔案的内容。這種方式每當靜态庫函數需要修改時就必須重新生成lib.a提供給使用者更換,比較麻煩,但有助于保密。
制作靜态庫
New Project -> iOS Library ->Cocoa touch Static Library 這樣就建立了一個靜态庫的工程,将你要打包成lib的.m,.h放到class目錄下面,然後選擇build就可以了.
Bulid之後,在工程目錄下Produces檔案夾下可以看到生成的.a檔案引用,右鍵,show in finder可以看到.a檔案。
要注意Build時的選項:
<1>iOS Device編譯出來的是在Debug-iphoneos目錄下,真機使用,終端,在該目錄下使用lipo -info **.a 可以檢視你到檔案類型為armv7等ARM架構。
<2>Simulator時編譯出來的是在Debug-iphonesimulator目錄下,模拟器使用,終端檢視類型顯示為i386架構。
可以使用lipo指令生成一個通用二進制lib.a lipo -create ****.a -output **Build/Products/
不同模式下可以生成不同類型的.a檔案 真機/模拟器與Debug/Release選項公交叉成4種.a檔案。
1.打包厘清楚是debug與Release的。
選擇debug與Release在Xcode工具欄的Product選項現則Scheme->Edit Scheme.然後為各個運作模式選擇選項。
2.厘清楚lib是i386(真機)或者ArmV7(模拟器)模式
終端下使用指令 lipo -info libPrint.a 可以檢視.a的屬性。如結果:libPrint.a is architecture(建構): armv7
3.把真機運作和模拟器運作的.a檔案合并生成通用的.a檔案,完成通用的靜态庫。
終端使用指令 lipo -create 真機.a路徑 模拟器.a路徑 -output 目标路徑(如/users/user/desktop/***.a)。然後info檢視合并後.a的資訊就會發現它已經同時具備了armv7和i386的條件
4.在Build Phases->Compile Source中的檔案,表示這些代碼會被編譯進lib中,你可以删掉你不希望被編譯的。
5.标準的Unix引入慣例是一個include檔案夾,用來存放所有引用的外部頭檔案,一個lib檔案夾用來存放庫檔案(.a)。這種檔案夾結構這是一種慣例,并不強制。
附:自動生成通用lib.a
生成通用二進制lib.a需要lipo,一個指令行工具,它允許在通用檔案上執行操作(類似于建立通用二進制, 列出通用檔案内容等等)。本教程中使用lipo的目的是聯合不同架構的二進制檔案到單個輸出檔案中。你可以直接在指令行中使用lipo指令,但在本教程中你可以讓Xcode執行一段建立通用庫的指令行腳本來為你做這件事。
Xcode中一個集合目标可以一次建構多個目标,包括指令行腳本。在選中lib工程檔案,點選+号增加新的Target,選擇iOS/Other并點選Aggregate,如下圖:
将目标命名為UniversalLib,確定選中你的lib工程中。然後選擇UniversalLib Target。切換到Build Phases标簽;點選+号增加Add Run Script Build Phase,如下圖:
現在你需要設定腳本項。展開Run Script子產品,在Shell行下粘貼如下代碼:
# define output folder environment variable
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal
TARGET_NAME=ProjectName
# Step 1. Build Device and Simulator versions
xcodebuild -target ${TARGET_NAME} ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}"
xcodebuild -target ${TARGET_NAME} -configuration ${CONFIGURATION} -sdk iphonesimulator -arch i386 BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}"
# make sure the output directory exists
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
# Step 2. Create universal binary file using lipo
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/lib${PROJECT_NAME}.a" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/lib${PROJECT_NAME}.a" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/lib${PROJECT_NAME}.a"
# Last touch. copy the header files. Just for convenience
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/include" "${UNIVERSAL_OUTPUTFOLDER}/"
注意:修改其中的TARGET_NAME=ProjectName為你的lib工程名。
代碼并不十分複雜,它是這樣工作的:
UNIVERSAL_OUTPUTFOLDER 包括了通用二進制包将要被存放的檔案夾:“Debug-universal”
Step 1. 第2行執行了xcodebuild并指令它建構ARM架構的二進制檔案。
下一行再次執行了xcodebuild指令并在另一個檔案夾中建構了一個針對Inter架構的iPhone模拟器的二進制檔案,在這裡關鍵參數是-sdk iphonesimulator -arch i386。
Step 2. 現在已經有了2個.a檔案分别對應兩個架構。執行lipo -create,用它們建立出一個通用二進制。
最後一行的作用是複制頭檔案到通用建構檔案夾的外層。
現在你已經準備好建構一個靜态庫的通用版本。
選擇UniversalLib然後Run,你就會在産品目錄發現一個新的檔案夾Debug-Universal或者Release-Universal,裡面包含了合并之後的lib.a以及頭檔案。
詳細操作參考連結:http://www.cocoachina.com/applenews/devnews/2013/1204/7468.html
二、引用lib工程
靜态庫工程被包含在項目工程中或者與項目工程放在同一個WorkSpace中,做成聯調靜态庫。這種方式的靜态庫工程與項目工程一起使用,故沒有對Libray中的代碼進行封裝,可以檢視修改。
建立聯調工程
1.在工程的Targets上右鍵.Add -> New Target -> Static Library 比如我們建了一個LibExample的target。這樣是一個工程包含多個Target的形式,沒有建立Lib工程。建立好Target之後你會發現原來的工程下面會多出幾個檔案夾:LibExample和LibExampleTest,用來存放跟Library相關的代碼。
另外,也可以直接在原工程上右鍵建立一個lib工程,或者在工程中右鍵add Existing File..增加已經存在的lib工程進來(不要選擇copy to folder)。這種形式是一個工程下面包含一個Lib工程。
2.在LibExample的目錄中增加你需要加入的.h.m檔案,然後檢視在Build Phases->Compile Source中的檔案,表示這些代碼會被編譯進lib中,你可以删掉你不希望被編譯的,增加你想要編譯進去的檔案。
3在工程的target上輕按兩下,targets->Build Phases裡面Target Dependencies裡面增加lib工程的target,這樣編譯工程時也會編譯lib工程生成lib.a檔案。同時在Link Binary With Libraries中增加選擇lib.a,表示對library庫的引用。
4.使用Lib工程而非Target時,需要修改工程的Scheme->Build中增增加Lib工程的Target。這樣才能編譯工程的同時編譯lib工程,生成.a。
5.引用lib頭檔案:在項目檔案工程檔案的target的build Setting->Header Search Paths中增加頭檔案路徑(../檔案名(lib工程檔案名/ 例如../MyLibPrint/),這個路徑适應于lib工程與項目工程在同一目錄),選擇成遞歸類型。
6.最後在工程中可以使用lib.a中的檔案了,使用時引用一下lib工程的頭檔案,如果不報錯說明頭檔案引用成功,然後就可以使用了。
Lib相關部分錯誤資訊
1.undefine symbols for architecture i386 錯誤。
其實這個錯誤原因很簡單,就是因為,我們用錯了編譯出來的libUITab.a lib,
在模拟器裡面,我們需要的是基于i386構架編譯的static lib,但是這個a檔案,大家還記得前面說的arm6 arm7構架的麼。這個a其實是在iphone這個arm構架上運作的代碼。
那如何編譯i386的庫呢?運作之前選擇Print>IOS Device,将這個iOS Device修改成iPhone5.0 Simulator。在進行編譯,這樣就可以編譯出i386下面的庫。
下面最多有四個檔案夾分别命名為:Debug-iphoneos/Debug-iphonesimulator/Release-iphoneos/Release-iphonesimulator這四條目錄每個目錄下同樣也有一個libPrint.a檔案。Release-iphoneos裡面的是基于arm6 arm7編譯出來的庫檔案。Release-iphonesimulator檔案夾下面的是基于i386編譯出來的檔案。
2.在編譯RegexKitLite的時候,報錯如下:
在項目的編譯設定中找到Other Linker Flags,然後在後面字段空白處輕按兩下,添加“-licucore”就可以了,引用正則架構必須打開此開關。-licucore,注意不要打錯,打錯了會報錯誤:clang: error: no such file or directory: ‘-licucore'
其他參考
Library官方文檔:https://developer.apple.com/library/ios/technotes/iOSStaticLibraries/Introduction.html#//apple_ref/doc/uid/TP40012554-CH1-SW1
XCode的各種參數配置參考:http://www.cnblogs.com/xiaodao/archive/2012/03/28/2422091.html