开发中会有一些常用的类或方法,或者是某个特定功能的,比如一个自定义的弹框、一个更容易使用的网络请求库,可以把它们放到一个单独的工程里,通过静态库(library、FrameWork)的方式应用到任何其他需要的项目里。就像使用百度地图sdk那样。
现在有一些文章介绍如何构建和使用自定义的静态库,但似乎没有说使用Workspace的。其实本质上,Workspace还是编译静态库然后给主工程使用,但不用先打开工程A,编译出libA.a,然后把文件拖到工程B,然后再工程B里面使用。主工程和它所用到的库工程是在同一个工作环境下(估计这就是Workspace的名字意思吧)。配置好了之后,你只需要运行主工程的target,会自动帮你编译需要的库。用过Pods库应该就明白。
好处就是:1.只需要打开一个工作环境,需要修改、同步代码,都不需要打开新的项目、新的文件,让人可以集中心思在代码上,在不同的项目里跳来跳去很容易打断思维的。
2.可以像同一个工程里一样,直接点击方法名查看引用库项目的代码,否则就要打开另一个项目,然后找到对应文件再找到方法。
3.只要运行自己的项目就行,就会自动帮你编译库文件。
下面以一个图书管理的demo来说WorkSpace的整个操作。
构建一个Workspace
如图选择构建一个WorkSpace,会生成.xcworkspace文件,以后就通过打开这个文件来打开WorkSpace。打开工程,会发现什么都没有,然后我们要添加各个工程(project)。在Xcode文管理文件的面板里,右键选择添加新文件。
当然,先要把项目建好。这里我建个项目叫BookManager,然后上面的添加文件,就把项目的BookManager.xcodeproj文件加进来就可以了。
重复上述动作,把所有需要的项目都加进来。这里我再建一个项目,用作对书籍的处理,假设这个库的作用是给一个URL,然后把书籍信息获取下来,并存到本地数据库,取名BookObtain吧。当然,这里建项目就要选择库类型了。
虽然添加项目是可以任意路径的,但是建议把所有要添加的项目放到同一个文件夹里,这样便于像header search paths这类的路径配置。
在BookObtain项目里构建了两个类,BookObtain负责获取书籍,Book是书籍的类。代码如下:
然后,现在我的项目里,想使用这个库里的获取书籍的功能,假设是写在ViewController这个类里,我在界面上加一个按钮,点击我就获取图书,然后把书籍信息显示到一个label里,就这么简单功能。
那其实就是调用BookObtain的+(Book*)obtainAndSaveBookWithURL:(NSString*)urlString方法,那要先导入头文件吧,发现#import"BookObtain.h" 报错,找不到头文件。那现在就遇到第一个问题:指定引用库的头文件路径。
在主项目的Build Settings 里找到Header Search Paths,添加一项$(SRCROOT)/../BookObtain,并且设置为recursive。$(SRCROOT)是当前的工程路径,..是返回上一层,然后到BookObtain文件夹。使用了相对路径,为了是项目移动不会影响这个配置,只要主工程和其他工程的相对位置不变,这里的相对位置是固定在同一个文件夹。
好了,添加代码:
- (IBAction)obtainBook:(UIButton*)sender {
Book* book = [BookObtainobtainAndSaveBookWithURL:@"xxx"];
NSLog(@"%@",book);
编译,报错:
Undefined symbols for architecture arm64:
"_OBJC_CLASS_$_BookObtain", referenced from:
objc-class-ref in ViewController.o
BookObtain这个类未定义,什么原因?
头文件#import,只是知道了头文件,但是源码不知道,BookObtain并没有被编译到,这时要把静态库添加到主工程里。
到主工程的Build Phases的Link Binary With Libraries里添加,点击“+”按钮,会给出整个Workspace可选的静态库,把BookObtain.a加进来就好了。这是第二个问题:添加静态库。
但是,还有一个大问题,那就是静态库是不能携带资源的,比如书籍如果没有获取到封面信息,就是用一个默认封面,那这个图片肯定是固定并且存放在BookObtain项目里,因为这个功能被做成静态库就是为了能够在多个项目里使用,如果每个使用的项目还得负责这个图片,那就违背了节省工作的初衷了。
这是第三个问题:怎么携带资源文件?
我知道的,有两种处理:1.使用bundle,这个东西本就是用来携带资源的,百度地图的sdk同时也携带一个bundle.这种呢,比较正规一些,麻烦的是资源就不是在mainBundle里面了,找图片啥的麻烦。
2.使用shell脚本,Xcode本身支持使用脚本做编译处理,脚本里做的事就是把资源文件编译到 xxx.app文件里面去,xxx.app目录就对应着mainBundle。
点“+”添加bundle,iOS那一类里没有,选OS X里的frameWork...,也因为这个,bundle建立后,要把Build Settings 里的Base SDK由OS X换成iOS。
然后为了编译项目的时候先把需要的bundle编译了再编译主工程的target,可以在Edit Scheme->Build里把bundle加进去,而且加到主工程target前面。
脚本拷贝资源,Pods是个很好的例子,它的脚本文件名叫Pods-resources.sh.里面写好了对各种资源类型的处理。
脚本使用就是在Build Phases里,添加一个新的组件,在顶端左边有个“+”,点开选择New Run Script Phase,
然后在脚本组件里,写入执行脚本的代码:
/Users/sh/Pods/Pods-resources.sh指定脚本文件,后面跟着的是给它的参数/Users/sh/Desktop/BookObtain/Resource。我们可以把需要拷贝的资源都放到一个文件夹里,然后把这个文件夹路径作为参数。脚本只要针对给定的文件路径做处理就可以了。