如果你一定要做靜態庫,就跳過這段吧。
本文沒什麼卵用啊,因為網上類似的參考資料很少哎。
是的,你沒看錯,因為可參考資料少,所以這篇並沒有什麼卵用。這不矛盾。為什麼這麼說咧?
參考資料少,是因為這個需求很少嘛。你看看那些大佬級別的庫,都是開源的,而且都是讓你直接用源碼的啊。
因為Windows下的DLL思想在移動端應用級別的App開發中,已經沒啥用了。特別是iOS,AppStore的APP禁止用動態庫的好麼。
當然,有時候我們沒有選擇,那就開始吧。
這裡我們要解決倆個問題:(1)靜態庫對其他庫的依賴的問題, (2)使用了category的問題
靜態庫可以是.a結尾的文件,也可以是.framework結尾的。網上很多資料,就不撸了。
如圖所示,填上項目名字就可以了,我的是GrouchyLibrary,Grouchy是“愛抱怨的”的意思:
(等等,這就結束了嗎?)沒有沒有,要解決前面說的兩個問題。
項目假設這樣一個場景:我們從服務器請求數據,服務器返回的數據格式一般是JSON或者ProtoBuffer、XML的。我們希望對於請求的使用者來說,我們都得到一個Dictionary,JSON被很多組件內置支持。因此,為了兼容服務器返回的不同格式,我們需要一個能把XML文檔處理成NSDictionary對象的工具。
首先,我們使用一個名為 GDataXMLNode的第三方組件。然後在此基礎上做一個返回NSDictionary對象的擴展:GDataXMLDocument (GrouchyParse)。
嗯,一圖勝千言,代碼的差不多是這樣的:
然後貼一下GDataXMLDocument (GrouchyParse)的代碼
// // GDataXMLDocument+GrouchyParse.h // KedllNetLibrary // // Created by Sendor.Wang on 16/8/9. // Copyright ? 2016年 Luke. All rights reserved. // #import "GDataXMLNode.h" @interface GDataXMLDocument (GrouchyParse) - (NSDictionary*)parse; @end // // GDataXMLDocument+GrouchyParse.m // KedllNetLibrary // // Created by Sendor.Wang on 16/8/9. // Copyright ? 2016年 Luke. All rights reserved. // #import "GDataXMLDocument+GrouchyParse.h" @implementation GDataXMLDocument (GrouchyParse) - (NSDictionary*)parse { NSMutableDictionary *rootDict = [[NSMutableDictionaryalloc]init]; GDataXMLElement *root = [selfrootElement]; NSArray * attributes = [rootattributes]; if (attributes.count >0) { NSMutableDictionary* attributesDict = [[NSMutableDictionaryalloc]initWithCapacity:attributes.count]; for (int i=0; i GDataXMLNode *node = attributes[i]; attributesDict[node.name] = node.stringValue; } rootDict[@"attributes"] = attributesDict; } NSArray * childrenArray = root.children; if (childrenArray.count >0) { for (int i=0; i GDataXMLNode *node = childrenArray[i]; [selfaddItem:[selfparseNode:node]withName:node.nametoDict:rootDict]; } } return rootDict; } - (id)parseNode:(GDataXMLNode *)node { if([node XMLNode]->type == XML_TEXT_NODE) { return [nodestringValue]; } else { NSMutableDictionary *dict = [[NSMutableDictionaryalloc]init]; GDataXMLElement *element = (GDataXMLElement*)node; NSArray * attributes = [elementattributes]; if (attributes.count >0) { NSMutableDictionary* attributesDict = [[NSMutableDictionaryalloc]initWithCapacity:attributes.count]; for (int i=0; i GDataXMLNode *node = attributes[i]; attributesDict[node.name] = node.stringValue; } dict[@"attributes"] = attributesDict; } NSArray * childrenArray = element.children; if (childrenArray.count >0) { for (int i=0; i GDataXMLNode *node = childrenArray[i]; [selfaddItem:[selfparseNode:node]withName:node.nametoDict:dict]; } } return dict; } } - (void)addItem:(NSDictionary *)item withName:(NSString*)name toDict:(NSMutableDictionary*)dict { NSString *arrayKey = [NSStringstringWithFormat:@"%@s", name]; if (dict[name]) { NSMutableArray* array = [[NSMutableArrayalloc]initWithArray:@[dict[name], item]]; dict[arrayKey] = array; [dict removeObjectForKey:name]; } elseif( dict[arrayKey] && [dict[arrayKey]isKindOfClass:[NSMutableArrayclass]]) { [dict[arrayKey] addObject:item]; } else { dict[name] = item; } } @end
別慌,看看錯誤信息:
GDataXMLNode.h:41:9: 'libxml/tree.h' file not found。
libxml/tree.h文件沒找到。這是什麼鬼。原來GDataXMLNode依賴於iOS提供的庫:libxml2.tbd.
[注意:我們如果在靜態庫中需要引用系統的動態庫或者第三方靜態庫的時候,一般(絕大部分情況下)不直接把靜態庫或者動態庫打入到我們的靜態庫包裡面, 而是由靜態庫的使用者在APP中添加引用]
鄭重其事的說了一通,其實就是說不要在靜態庫中引入libxml2.tbd。其實不管引不引入,都要解決'libxml/tree.h文件的問題。Google了一下(其實是bing,由於網絡環境不好,用google的機會很少),這個文件位於XCode SDK目錄下的usr/include/libxml2中,需要這樣設置
左鍵點擊左側的項目,選擇targets -> build settings -> search paths -> header search paths, 在右邊雙擊,彈出編輯框:
點擊“+”, 輸入${SDK_DIR}/usr/include/libxml2,再次編譯,ok.
和Java不同,Object C 需要輸出頭文件供使用者引入,就像我們剛才引入libxm/tree.h一樣。
切換到Build Phases, 展開下方的Copy files, 點“+”, 在彈出的窗口中選擇要導出的.h文件,以及這些文件引用的頭文件(遞歸),一般就是所有.h文件。
就是建一個普通的APP工程,就不貼圖和步驟了。如果你忘了把他們放在一個workspace裡面,關掉其中一個,把另一個工程文件從find裡面拖到XCode裡面就可以了。
我建的是個Single View Application, 在ViewController.m 裡面輸入如下代碼:
#import "ViewController.h" #import "GDataXMLDocument+GrouchyParse.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [superviewDidLoad]; // Do any additional setup after loading the view, typically from a nib. NSString *xmlString =@"Hello World!"; GDataXMLDocument *xmlDoc = [[GDataXMLDocumentalloc]initWithXMLString:xmlStringencoding:NSUTF8StringEncodingerror:nil]; NSLog(@"%@", [xmlDocparse]); } - (void)didReceiveMemoryWarning { [superdidReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end
然後我們配置下測試工程:
我的配置如下(根據倆項目的位置不同而有所不同, 我的測試工程和庫工程並列在一個文件夾內)
Library Search Paths:$(PROJECT_DIR)/../GrouchyLibrary/build/Debug-iphonesimulator
User Header Search Paths:$(PROJECT_DIR)/../GrouchyLibrary/build/Debug-iphonesimulator/include/GrouchyLibrary
結果是編譯成功,運行失敗
提示的意思是說,沒有找到parse方法,這個方法我們是寫在category裡面的。一番搜索之後,給App加了個配置:
Targets -> build settings -> Linking -> Other Linker Flags : -all_load
運行成功,我們得到了正確的結果。
(3)不過,事情還沒完
插一個調試設備,在設備下編譯運行,失敗了,說沒有找到arm7/7s/64架構的庫。
記得當初做了如下配置, Library Search Paths:$(PROJECT_DIR)/../GrouchyLibrary/build/Debug-iphonesimulator
我們再為設備增加一個配置, 把上面的simulator改成os, 然後在模擬器下清空庫的編譯結果,再切換至設備下,編譯鏈接成功。
如果經常使用的是設備,就把設備的搜索放在模擬器的上面即可。
再說一次,很多時候真的沒有必要做靜態庫。XCode 搭配Git或者SVN等都提供了好方法,讓我們能在不同的工程中使用同一段庫代碼。而且庫創建的初期,肯定會有很多bug,所以更多場景是想測試工程那樣使用,還不如直接就使用源碼,bug改起來也方便。
所以,我想你做靜態庫肯定有不可抗拒的原因。
經過大家一段時期的狂虐之後,我們假設這個庫的bug已經很少了,那麼來發布吧。
切換Release版本的方法如下:在Run和Stop按鈕的右邊有一個工程名,點擊工程名,選擇Edit Scheme
左側選擇Run ProjectName.app 右側選擇Info頁,在Build Configuration選擇相對應你需要生成的版本就行了。
編譯後,在build目錄有Release的兩個子目錄,分別是os和simulator,對應設備和模擬器。
使用lipo組合一個同時包含模擬器和設備的庫文件(.a文件),就可以發布下這個.a文件了。當然別忘了發布頭文件哦。
使用者link這個.a文件,在模擬器和設備上都能玩。
他們發布應用的時候,用lipo分離出只支持設備的庫文件,link到app中發布。(假設他們會玩lipo, 你也可以吧單獨的僅支持設備的庫文件一起發布,使用者發布app的時候,替換下就ok。)
怎麼用lipo,這裡就不撸了,網上到處都是啊。