ios靜態庫的使用
ios靜態庫的使用 2014-03-11 22:24 3906人閱讀 評論(0) 收藏 舉報
ios的靜態庫檔案是*.a,如果需要使用它,我今天學的簡單的方法,可通過,簡單說說,如果有一個A手機項目,一個B的靜態庫項目,A想使用B.a,按以下的步驟:
1、在A項目裡面拖進B項目。在B的product下面會看見紅色的B.a,表示還沒有編譯通過(在工程設定裡添加上你需要匯出的.m檔案)。
2、選擇好需要編譯的對象,B下的某模擬器或者是B下的真機上進行編譯(模擬器上產生的靜態庫和真機上產生的不能混用)
3、在A裡面建立一個檔案夾(new group),裡面拖進你需要B裡面匯入的標頭檔。
4、在A的framework裡加入編譯好的.a靜態庫,編譯通過的就不會是紅色。
5、在需要使用的地方#import 所需的標頭檔。ok了!
下面二篇,是別人寫的產生和使用靜態庫,裡面還有資源的Binder 方法,可借鑒!
一、IOS開發----產生靜態庫(.a)
由於iPhone控制項的極度匱乏和自訂群組件在重用上的限制,在過去的項目中我們積累了大量的“純程式碼”組件——因為IB本身的限制,我們無法把這些組件封裝為IB組件庫(本來我們想通過分發xib檔案的方式重用這些組件,但最終發現這根本不可能,蘋果的Plug-in編程不支援iPhone)。
最終我們想到了靜態庫。雖然這仍然還是一種比較原始的複用方式,但起碼我們可以隱藏組件的原始碼。
下面, 我們使用iPhone靜態庫把自訂群組件CheckButton 進行進一步的封裝。(組件的實現參考前一篇博文《自訂控制項複選框和單選框的實現》)
一、實現靜態庫
建立工程, 選擇 Library 下的 “ Cocoa Touch Static Library ” 。給工程命名,例如:yhyLibrary。
複製CheckButton組件的4個源檔案:CheckButton.h、CheckButton.m、RadioGroup.h、RadioGroup.m到Classes目錄下,同時把CheckButton的4個資源檔:check.png、uncheck.png、radio.png、unradio.png,複製到工程檔案夾。
按下 ? +b編譯,在Products目錄下即產生一個 .a檔案。
二、 建立資源束
靜態庫中並不能包含資源檔,雖然我們已經把4個資源檔(.png檔案)拷貝到靜態庫工程中,但實際上這些.png是不會添加到target的,也就是說編譯結果中並不包含這些資源,因此如果此時調用靜態庫,所有的資源(字串、圖片)都是缺失的。
我們可以把資源建立成單獨的束(Bundle)。
建立工程“ Mac OS X -> Framework & Library -> Bundle ”,命名為:yhyLibraryBundle。
然後把上面4個.png檔案拷進Resouces中去。編譯,產生yhyLibraryBundle.bundle檔案。
返回靜態庫工程,建立一個類:Utils 。
編輯Utils.h:
#define MYBUNDLE_NAME @ "yhyLibraryBundle.bundle"
#define MYBUNDLE_PATH [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: MYBUNDLE_NAME]
#define MYBUNDLE [NSBundle bundleWithPath: MYBUNDLE_PATH]
NSString * getMyBundlePath( NSString * filename);
編輯Utils.m:
#import "Utils.h"
NSString* getMyBundlePath( NSString * filename)
{
NSBundle * libBundle = MYBUNDLE ;
if ( libBundle && filename ){
NSString * s=[[libBundle resourcePath ] stringByAppendingPathComponent : filename];
NSLog ( @"%@" ,s);
return s;
}
return nil ;
}
函數getMyBundlePath可以取得束yhyLibraryBundle中具體資源的絕對檔案路徑,如:
/Users/kmyhy/Library/Application Support/iPhone Simulator/4.2/Applications/8213652F-A47E-456A-A7BB-4CD40892B66D/yhyLibTest.app/yhyLibraryBundle.bundle/Contents/Resources/radio.png
同時,修改CheckButton.m中的代碼,匯入Utils.h標頭檔,把其中擷取圖片的代碼由imageNamed修改為imageWithContentsOfFile,如:
[ icon setImage :[ UIImage imageWithContentsOfFile : getMyBundlePath ( checkname )]];
即通過絕對路徑讀取圖片資源。
除了這種方法,我們還可以有一個簡單辦法,就是把4個資源檔直接拷貝到你調用靜態庫的應用工程中(不需要修改靜態庫代碼)。
三、靜態庫調用
1、添加靜態庫
建立Window-based Application工程,給工程命名,如yhyLibraryTest。
右鍵點 Frameworks->Add->Existing Files.. ,把靜態庫工程的yhyLibrary.xcodeproj檔案 添加到當前工程(不要選擇 Copy items ) 。
選中添加進來的yhyLibrary.xcodeproj檔案,勾選“include to target”選項,如,打上最後一個小勾:
2、添加Direct Dependencies(即引用工程)
類似於Visual Studio中的引用工程,目的是便於在本工程中直接編輯所引用的靜態庫工程,以便對靜態庫進行修改。
在“ Targets ”目錄下選擇“ FirstLibraryTest ”,點擊“info”按鈕,調出目標的屬性視窗,切換到“General”欄,點擊“ Direct Dependencies ”下方的“ + ”按鈕,將工程靜態庫libyhyLibrary添加到Direct Dependencies中,結果如:
3、添加標頭檔搜尋路徑
開啟工程的info視窗,在Build欄中找到Header Search Paths,添加字串“../yhyLibrary”。
4、 引用資源束
在target的Copy Bundle Resources上右鍵,選擇“Add->Existing File…”,把前面產生的yhyLibraryBundle.bundle束添加到工程。
5、調用靜態庫中的類
編輯 application:( UIApplication *)application didFinishLaunchingWithOptions: 方法中的代碼:
// 選項按鈕組
RadioGroup * rg =[[ RadioGroup alloc ] init ];
// 第 1 個選項按鈕
CheckButton * cb=[[ CheckButton alloc ] initWithFrame : CGRectMake ( 20 , 60 , 260 , 32 )];
// 把選項按鈕加入按鈕組
[ rg add :cb];
cb. label . text = @"★" ;
cb. value =[[ NSNumber alloc ] initWithInt : 1 ];
// 把按鈕設定為選項按鈕樣式
cb. style = CheckButtonStyleRadio ;
// 加入視圖
[ self . window addSubview :cb];
[cb release ]; //add 後,會自動持有,可以釋放
// 第 2 個選項按鈕
cb=[[ CheckButton alloc ] initWithFrame : CGRectMake ( 20 , 100 , 260 , 32 )];
[ rg add :cb];
cb. label . text = @"★★" ;
cb. value =[[ NSNumber alloc ] initWithInt : 2 ];
cb. style = CheckButtonStyleRadio ;
[ self . window addSubview :cb];
[cb release ];
// 第 3 個選項按鈕
cb=[[ CheckButton alloc ] initWithFrame : CGRectMake ( 20 , 140 , 260 , 32 )];
[ rg add :cb];
cb. label . text = @"★★★" ;
cb. value =[[ NSNumber alloc ] initWithInt : 3 ];
cb. style = CheckButtonStyleRadio ;
[ self . window addSubview :cb];
[cb release ];
運行結果如下:
6、分發靜態庫
將產生的.a檔案和.bundle檔案打包分發給其他人。
二、使用靜態連結庫(Xcode4.6.2)
一、理論部分
在實際的編程過程中,通常會把一些公用函數製成函數庫,供其它程式使用,一則提搞了代碼的複用;二則提搞了核心技術的保密程度。所以在實際的項目開發中,經常會使用到函數庫,函數庫分為靜態庫和動態庫兩種。和多數人所熟悉的動態語言和靜態語言一樣,這裡的所謂靜態和動態是相對編譯期和運行期的:靜態庫在程式編譯時間會被連結到目標代碼中,程式運行時將不再需要改靜態庫;而動態庫在程式編譯時間並不會被連結到目標代碼中,只是在程式運行時才被載入,因為在程式運行期間還需要動態庫的存在。
靜態連結庫適用於:
1.你想將一部分以後都不會修改的代碼打包,供其他項目使用
2.你想將一部分代碼封裝起來給別人用,又不願別人看到你的實現方法
二、實踐部分
如何製作靜態連結庫(以下簡稱lib):
1。如果是新工程。建立工程的時候選Framework&Library -> cocoa touch static library,就直接建立了一個靜態連結庫工程,預設會有兩個跟工程名相同的.h和.m,繼續添加檔案,m都會自動加入到Build Phases->Compile Source中,表示這些代碼會被編譯進lib中,你可以刪掉你不希望被編譯的。
2. 如果是項目工程,想抽取一個lib出來,就add target,也是選Framework&Library -> cocoa touch static library。在xcode navigator裡會多一個檔案夾,和你新建立的target同名。同樣,你可以在Build Phases->Compile Source裡,添加你希望加入到lib中的檔案。
下面:建立兩個單視圖模版項目DemoOne,DemoTwo,其中,我想把DemoTwo作為靜態庫,然後在DemoOne中使用:
a、開啟DemoTwo
滑鼠選擇:
然後 點擊Add Target,選擇 Framework & Library -> Cocoa Touch Static Library -> 建立一個名字叫MyLib的庫:
其中,MyLib這個target,就是我們想對外提供的庫,這個庫的對外提供的介面,是我們自己可以任意控制的,當然可以加多個target,每個target靜態庫可以提供不同介面,我這裡只做一個靜態庫MyLib。讓MyLib這個target 和 DemoTwo 建立時的預設target DemoTwo功能類似,所以還要給MyLib 添加 Frameworks:
然後開始編寫MyLib這個庫想對外提供哪些功能了,在DemoTwo項目中建立一個group,命名為LibMethod,並在其中建立三個類Func1,Funk2,UILabelEX,其中實現的代碼都很簡單,列印log而已。
Func1 和 Func2 類似,拿Func1舉例:
@interface Func1 : NSObject
- (void) func1Log;
@end
#import "Func1.h"
@implementation Func1
- (void) func1Log {
NSLog(@"Func1 log");
}
@end
UILabelEX 是一個類別擴充,因為之前有人說,類別擴充不能放到靜態庫中,所以親自實驗一下:
#import
@interface UILabel (TestColor)
- (void) testMethodColor;
@end
#import "UILabelEX.h"
@implementation UILabel (TestColor)
- (void) testMethodColor {
NSLog(@"testMethodColor");
}
@end
並且確保MyLib 的 Compile Sources 中包含我們剛剛建立類的 .m檔案,因為這裡添加了哪些.m檔案,就相當於MyLib靜態庫對外提供了什麼介面,如果沒有加入,就要手動點擊+來加入了:
然後關閉DemoTwo項目,開啟DemoOne項目,開啟DemoTwo專案檔夾,把其中的 DemoTwo.xcodeproj 拖拽到DemoOne中:
然後給DemoOne添加庫,選擇我們在DemoTwo中建立的MyLib:
如果libMyLib.a為紅色,表明DemoTwo,沒有編譯產生libMyLib.a,不要慌,這個是小事情:
理論(在編譯之前,在target的scheme中選build configuration選擇模擬器,然後編譯。
注意,你用device模式編譯出的lib只能真機運行,模擬器模式編譯出的lib只能用於模擬器調試。然後找到編譯出lib,複製到需要它的工程裡。
如果你希望一個lib既可以在模擬器上運行,又可以在真機上運行,那就各編譯一次吧,把兩個lib都找到,用命令把兩個lib合并成一個,命令是:lipo -create sim.a dev.a -ouput libXX.a 合并產生的libXX.a就可以兩用了。
把lib和新工程裡需要引用的標頭檔都添加進新工程,這樣就可以了。)
我這裡以使用模擬器為例,來讓DemoTwo編譯產生lib
這個有個細節問題,就是你產生的lib想用在真機,還是模擬器?很簡單, 首先選擇 Mylib,然後在點擊其響應下的 Edit Scheme,最上邊可以選擇是模擬器還是真機,然後build一下:
此時發現DemoOne中的,依賴庫正常了吧:
然後就開始讓DemoOne來使用DemoTwo提供的借口吧:
單首先要在DemoOne中引入下DemoTwo中的介面,DemoOne建立group ,名字 lib,然後把DemoTwo中的介面.h檔案,以引用的形式拖拽到lib檔案夾中(如果不以引用形式,當DemoTwo中介面代碼改變時,DemoOne中的介面檔案不會隨著改變):
到此時,工作基本完成了,然後在DemoOne的 ViewController中實現如下代碼:
#import "ViewController.h"
#import "Func1.h"
#import "Func2.h"
@implementation ViewController
- (void)viewDidLoad
{
[superviewDidLoad];
Func1 *obj1 = [[Func1alloc]init];
[obj1 func1Log];
Func2 *obj2 = [[Func2alloc]init];
[obj2 func2Log];
}
@end
編譯及運行,不出意外,應該有log列印了,說明我們基本成功了。
不過是不是少了點是嗎?對 #import "UILabelEX.h" 這個類還沒使用呢,這個類是對UILabel的類型擴充:
#import
@interface UILabel (TestColor)
- (void) testMethodColor;
@end
#import "UILabelEX.h"
@implementation UILabel (TestColor)
- (void) testMethodColor {
NSLog(@"testMethodColor");
}
@end
那使用一下吧:
UILabel *obj3 = [[UILabel alloc]init];
[obj3 testMethodColor];
然後編譯並運行,發現項目crash了,原因:
-[UILabel testMethodColor]: unrecognized selector sent to instance 0x7574190
代碼裡明明
UILabel可以找到testMethodColor,但實際上沒找到這個方法,這個地方還有個細節,答案在這裡:http://developer.apple.com/library/mac/#qa/qa1490/_index.html
DemoOne的 build setting中,搜尋 Other Linker Flags,找到設定後,在其中添加一個參數 -ObjC,再編譯及運行,貌似一切都OK了。
其它:
1、如果你沒有完全按步驟來弄,可能會出現如下錯誤,一般用模擬器做的項目可能會遇到這個問題:
- Undefined symbols for architecture i386:
- "_OBJC_CLASS_$_RequestServer", referenced from:
- objc-class-ref in ListViewController.o
- objc-class-ref in SettingsViewController.o
- ld: symbol(s) not found for architecture i386
- clang: error: linker command failed with exit code 1 (use -v to see invocation) 不要擔心,去DemoTwo的lib中,把對外介面的.m檔案添加進去:
2、如果做真機使用的 lib,可能會遇到arm7相關的錯誤,我暫時沒遇到,引用別人的解決辦法如下(我沒親自實驗過):
Xcode4.5.2、iOS6應用中靜態庫不支援armv7s的解決方案
錯誤詳細資料:
ld: file is universal (3 slices) but does not contain a(n) armv7s slice: /zhangyg/XXX/XXX/libs/libxxx.a for architecture armv7s
clang: error: linker command failed with exit code 1 (use -v to see invocation)
解決方案如下:
方法一:把Build Active Architecture Only的值設定為Yes
方法二:把Valid Architectures中的armv7s刪除
方法三:如果有libxxx.a的原始碼再編譯一個libxxx.a的armv7s版本,然後合并到之前的libxxx.a中
3、模擬器運行正常,但真機會crash,列印如下錯誤:
dyld: lazy symbol binding failed: Symbol not found: _objc_setProperty_atomic_copy
Referenced from: /var/mobile/Applications/DFCB17A5-52AC-41CD-9ECB-94415C7D36F3/kalagame-demo.app/kalagame-demo
Expected in: /usr/lib/libobjc.A.dylib
dyld: Symbol not found: _objc_setProperty_atomic_copy
Referenced from: /var/mobile/Applications/DFCB17A5-52AC-41CD-9ECB-94415C7D36F3/kalagame-demo.app/kalagame-demo
Expected in: /usr/lib/libobjc.A.dylib
解決辦法:
這個錯誤就是說App可執行檔裡引用了objc_setProperty_nonatomic或objc_setProperty_atomic這些函數。但是代碼裡顯然沒有直接調用這2個函數,應該是系統在編譯時間產生的。經過Debug調試發現總是在設定一個對象的屬性時出現這個錯誤。而這個對象的類定義在靜態庫裡面,所以我看了看靜態庫。
經過排查,發現導致這一問題的原因是這個靜態庫的Deployment Target設定成了6.0。因為objc_setProperty_nonatomic和objc_setProperty_atomic是iOS6中新增的函數,所以如果靜態庫的Deployment Target設定成iOS6,那麼編譯後就會使用objc_setProperty_nonatomic和objc_setProperty_atomic這些新的API。由於iOS5中沒有這些API,運行後將會崩潰。
結論
靜態庫在編譯時間,Deployment Target一定要低於和等於工程的Deployment Target。否則容易出現低版本iOS運行不相容的情況。
4、突然項目要更改靜態庫項目的工程名稱,於是右件靜態庫項目可執行檔(藍顏色的那個),快顯功能表中選 Show File Inspector,然後XIB屬性修改入口彈出,可以修改項目名稱,但修改完項目名稱之後,發現靜態庫提供的幾個介面失效了。
報錯:Undefined symbols for architecture armv7:
解決辦法:在引用靜態庫的項目設定中,重新添加靜態庫檔案,就是那個 XXX.a 檔案。