【由于水平有限,文中错误在所难免,希望能指出和不吝赐教】
现在时间是 2016-08-22 17:48, 我的系统是OS X 10.11.6, XCode版本是7.3(7D175)
0 前言
如果你一定要做静态库,就跳过这段吧。
本文没什么卵用啊,因为网上类似的参考资料很少哎。
是的,你没看错,因为可参考资料少,所以这篇并没有什么卵用。这不矛盾。为什么这么说咧?
参考资料少,是因为这个需求很少嘛。你看看那些大佬级别的库,都是开源的,而且都是让你直接用源码的啊。
因为Windows下的DLL思想在移动端应用级别的App开发中,已经没啥用了。特别是iOS,AppStore的APP禁止用动态库的好么。
当然,有时候我们没有选择,那就开始吧。
这里我们要解决俩个问题:(1)静态库对其他库的依赖的问题, (2)使用了category的问题
1 创建一个静态库工程
静态库可以是.a结尾的文件,也可以是.framework结尾的。网上很多资料,就不撸了。
(1)创建
如图所示,填上项目名字就可以了,我的是GrouchyLibrary,Grouchy是“爱抱怨的”的意思:
(2)然后么就开始写代码了额
(等等,这就结束了吗?)没有没有,要解决前面说的两个问题。
项目假设这样一个场景:我们从服务器请求数据,服务器返回的数据格式一般是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<attributes.count; 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<childrenArray.count; 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<attributes.count; 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<childrenArray.count; 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
(3)然后编译,不通过
别慌,看看错误信息:
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.
(4)输出.h文件
和Java不同,Object C 需要输出头文件供使用者引入,就像我们刚才引入libxm/tree.h一样。
切换到Build Phases, 展开下方的Copy files, 点“+”, 在弹出的窗口中选择要导出的.h文件,以及这些文件引用的头文件(递归),一般就是所有.h文件。
2 静态库工程就算创建好了,下面我们来创建一个测试工程
(1)创建配置工程
就是建一个普通的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 =@"<?xml version=\"1.0\"?><note from=\"John\" to=\"George\">Hello World!</note>";
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
(2)编译运行
结果是编译成功,运行失败
提示的意思是说,没有找到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, 然后在模拟器下清空库的编译结果,再切换至设备下,编译链接成功。
如果经常使用的是设备,就把设备的搜索放在模拟器的上面即可。
3 发布
再说一次,很多时候真的没有必要做静态库。XCode 搭配Git或者SVN等都提供了好方法,让我们能在不同的工程中使用同一段库代码。而且库创建的初期,肯定会有很多bug,所以更多场景是想测试工程那样使用,还不如直接就使用源码,bug改起来也方便。
所以,我想你做静态库肯定有不可抗拒的原因。
经过大家一段时期的狂虐之后,我们假设这个库的bug已经很少了,那么来发布吧。
(1)编译Release版本
切换Release版本的方法如下:在Run和Stop按钮的右边有一个工程名,点击工程名,选择Edit Scheme
左侧选择Run ProjectName.app 右侧选择Info页,在Build Configuration选择相对应你需要生成的版本就行了。
编译后,在build目录有Release的两个子目录,分别是os和simulator,对应设备和模拟器。
(2)使用lipo组合一下,然后发布
使用lipo组合一个同时包含模拟器和设备的库文件(.a文件),就可以发布下这个.a文件了。当然别忘了发布头文件哦。
使用者link这个.a文件,在模拟器和设备上都能玩。
他们发布应用的时候,用lipo分离出只支持设备的库文件,link到app中发布。(假设他们会玩lipo, 你也可以吧单独的仅支持设备的库文件一起发布,使用者发布app的时候,替换下就ok。)
怎么用lipo,这里就不撸了,网上到处都是啊。
最后,放一下示例代码