demo地址开发中会有一些常用的类或方法,或者是某个特定功能的,比如一个自定义的弹框、一个更容易使用的网络请求库,可以把它们放到一个单独的工程里,通过静态库(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。我们可以把需要拷贝的资源都放到一个文件夹里,然后把这个文件夹路径作为参数。脚本只要针对给定的文件路径做处理就可以了。
更新
编译的时候,是否会自动编译依赖项目?是否会更新依赖的bundle的问题,Xcode9上测试
资源包bundle
1. 只添加到Copy Bundle Resources里,是不会自动编译资源包的
2. 如果自己主动编译了这个包,因为workspace里的项目公用一个目标位置,所以主项目这时可以得到资源包;
3. 以上面这种方式通过编译后,clean project之后再编译bundle会自动回来,但是如果修改了原项目的bundle内容,这种方式不会跟随更新。所以它其实更像是保留了一份数据然后拷贝过来,还是原来的bundle,并不是重新编译的
4. 想主项目编译的时候自动更新bundle,需要在Edit scheme-> Build里加入bundle,这样点击build的时候会同时编译多个项目。这样bundle会更新。
在build这里处理有几个注意的点:
1. Parallelize Build并行编译,应该是同时编译多个target
2. Find Implicit Dependencies查找隐性的依赖,在这些target之中,有一些会依赖另一些,就会在编译的时候把依赖的库一起带上。
3. 几个target的顺序,它是按照从上到下严格执行的。
如果把bundle的编译放在主项目后面:
只开启Find Implicit Dependencies, clean project第一次编译报错,找不到bundle;之后编译会好,但是如果修改了bundle之后第一次是没效果的,第二次才有效果。
得出的猜测是:主项目在前面所以先编译主项目,然后主项目编译的时候把bundle带进去,clean project之后bundle清空了,所以报错;之后修改bundle内容,在编译主项目的时候,因为bundle还没重新编译,所以拿到的是旧的内容,所以要在第二次才会生效。
总结来说,虽然勾选了依赖,但是编译顺序还是不变
如果再同时开启Parallelize Build,一切正常,说明这时Xcode重新调整了编译顺序,把被依赖的bundle放到了主项目前面。
所以Parallelize Build的意思并不是所有的项目全部同时开始编译,而是要考虑Find Implicit Dependencies的,会从那些没有依赖的开始编译。我猜测,按照依赖关系可以形成一个图,用图算法就可以达到这个需求。
然后也可以我们自己手动调整,把bundle放在主项目的前面。这样先把bundle编译了,一切都没问题了。
静态库文件
只要加在了Link Binary With Libraries里,Xcode就会自动寻找隐性的依赖。文档里:
Xcode examines the files in the build directory to discover implicit dependencies.
动态库
1. 修改Runpath search Paths,设定动态库链接的位置,默认是@executable_path/frameWorks,但workspace会把其他项目的目标都输出到@executable_path/,所以修改一下。否则报错dyld: Library not loaded:xxx Reason: image not found
2. 除了把动态库加入到Link Binary With Libraries,还需要加入到Copy Bundle Resurces里。
3. 动态库需要签名,就跟APP一下,否则报错Reason: no suitable image found. Did find: xxxFramework: required code signature missing