最近整理一些项目以前依赖留下的问题,在使用 CocoaPods 和 Carthage 的时候引出了关于静态库和动态库的思考,手动编译静态库的朋友应该知道,如果自己编译了一个静态库,这个静态库依赖了 iOS 自带的库,即使你在Xcode开发工具http://www.maiziedu.com/course/234/中显式指定了依赖,当你在另一个工程中使用这个静态库的时候,依旧需要显式在那个工程指定这个静态库所依赖的系统库。
举个栗子
AFNetworking 是个好东西,大家都喜欢用,在日常使用的时候,一般都是使用 CocoaPods 引入这个库,笔者前面有篇文章分析了 CocoaPods 做了什么工作,但是却没有分析 CocoaPods 为何如此设计,这里先卖个关子不讲。AFNetworking 依赖Security.framework MobileCoreServices.frameworkSystemConfiguration.framework 三个系统框架,如果我们将其编译为静态库,然后这个静态库被其他工程依赖,那么其他工程依旧需要引入这三个框架。如果我们将其编译为动态库,则不需要依赖这三个框架。这个情况引起了笔者的兴趣
分析
既然问题是编译链接,那么就需要从动态库和静态库入手分析,首先,我们需要明确的是,系统提供的 framework 都是动态库的形式提供的。这很好理解,因为 UIKit 这类 framework 被使用的太频繁了,内存中完全只需要保留一份副本。这样也能减轻 App 的大小。那么直接来着手模拟一下其编译过程。
模拟静态库的编译
创建一个动态库用于模拟系统库
dynamic.h
----------void hello();
dynamic.c
----------#include "dynamic.h"#include
void hello() {
printf("Hello World");
}
然后编译打包动态库
> gcc -c -o dynamic.o dynamic.c # 编译为对象文件
> gcc -shared dynamic.o -o libdynamic.so
创建一个静态库用于模拟第三方静态库
static.h
--------void sayHello();
static.c
--------#include "static.h"#include "dynamic.h"
void sayHello() {
hello();
}
然后编译静态库
> gcc -c -o static.o static.c
> ar -r libstatic.a static.o
这里大家有没有发现一个问题,静态库实际上并没有链接动态库,仅仅只是 include 了一个头文件用于编译通过。最终生成的静态库根本不知道动态库的存在。然后创建一个带有main 函数的程序
hello.c
-------#include "static.h"int main(int argc, char *argv[]) {
sayHello();
}
然后编译
> gcc -o hello hello.c -L. -lstatic# 直接报错没有找到 hello() 的二进制代码
Undefined symbols for architecture x86_64:
"_hello", referenced from:
_sayHello in libstatic.a(static.o)
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
模拟动态库的编译
创建一个动态库用于模拟系统库
dynamic1.h
----------void hello();
dynamic1.c
----------#include "dynamic1.h"#include
void hello() {
printf("Hello World");
}
然后编译打包动态库
> gcc -c -o dynamic1.o dynamic1.c # 编译为对象文件
> gcc -shared dynamic1.o -o libdynamic1.so
创建动态库用于模拟第三方动态库
dynamic2.h
--------void sayHello();
dynamic2.c
--------#include "dynamic2.h"#include "dynamic1.h"
void sayHello() {
hello();
}
然后编译打包动态库
> gcc -c -o dynamic2.o dynamic2.c # 编译为对象文件
> gcc -shared dynamic2.o -o libdynamic2.so# 报错,显示 dynamic2.o 中没有 hello() 二进制代码
Undefined symbols for architecture x86_64:
"_hello", referenced from:
_sayHello in dynamic2.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
> gcc -shared dynamic2.o -o libdynamic2.so -L. -ldynamic1# 成功,无报错
然后创建一个带有 main 函数的程序
hello.c
-------#include "static.h"int main(int argc, char *argv[]) {
sayHello();
}
然后编译
> gcc -o hello hello.c -L. -ldynamic2
总结
从上面两个编译过程大家应该也能明白了,静态库实际上只是对象文件的打包,也就是说,只经过了编译过程,而没有链接过程,编译一个静态库甚至只需要满足所有函数的声明就行,而动态库虽然没有经过正规的链接,但是实际上还是通过 gcc 做了跟其他动态库的链接,动态库自身有了依赖的概念。所以不需要在工程中显式依赖了。这里可能有朋友想要问,这种知识有什么用处?实际上这种知识在依赖管理中有用,前面说过 CocoaPods 对工程修改了很多内容,这是有原因的,因为 iOS8 之前是不存在动态库的,只存在静态库,CocoaPods 不得不对目标工程也作出修改来添加对其他库的依赖,因为静态库不知道自身依赖什么库。而 Carthage 则只支持 iOS8 和动态库,所以完全可以没有侵入性,只需要提供一个动态库,然后工程依赖这个动态库就行了。
原文来自:推酷