天天看點

iOS中建立使用連結庫

一、動态連結庫與靜态連結庫的差別

  庫從本質上來說是一種可執行代碼的二進制格式,可以被載入記憶體中執行。庫分靜态庫和動态庫兩種。 

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下的檔案。安裝完畢如下圖:

iOS中建立使用連結庫

分别運作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

iOS中建立使用連結庫
iOS中建立使用連結庫

2、把libsql.h和libsql.m删除。導入ocsqlite.h和ocsqlite.c(檔案見http://blog.csdn.net/fengsh998/article/details/8278978)

iOS中建立使用連結庫

3、修改scheme,設為release版本。

iOS中建立使用連結庫

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

如圖:

iOS中建立使用連結庫

模拟器的:liblibsql.a檔案資訊。

input file liblibsql.a is not a fat file

Non-fat file:  liblibsql.a  is architecture: i386

如圖:

iOS中建立使用連結庫

如果使用真機和模拟器通用,則需要将這兩個檔案合并,使用指令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中建立使用連結庫

八、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 -->輸入動态庫的相關資訊

iOS中建立使用連結庫

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

iOS中建立使用連結庫

4:在動态庫的相關類中添加一些測試方法

5:删除多餘的framework檔案。值添加Foundation.framework檔案

iOS中建立使用連結庫
iOS中建立使用連結庫

Xcode--->Preferences--->Locations--->Advanced---->

iOS中建立使用連結庫

這樣基本就可以編譯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,如下圖:

iOS中建立使用連結庫

将目标命名為UniversalLib,確定選中你的lib工程中。然後選擇UniversalLib Target。切換到Build Phases标簽;點選+号增加Add Run Script Build Phase,如下圖:

iOS中建立使用連結庫

現在你需要設定腳本項。展開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