前言
三个月没写东西了,是时候总结点东西了。SDK这个东西开发其实和项目开发类似,但是项目中你不需要关注一些配置和打包的参数,或者你压根不需要关注Framework和.a到底是怎么链接配置的,因为Cocoapods或者你拖进Xcode已经帮你自动生成配置了,趁着周末这良辰美景,翻了几篇文章,做个总结和记录,文章不知道会分几篇,但是一片理论介绍,一篇Demo制作应该会有的,由于网上的Demo都是很简单的介绍给你怎么制作Framework或者静态库,没有第三方依赖,也没有静态库依赖,第二篇会做一个Demo比如又有Masonry依赖,又有微信支付.a的依赖,到时候会介绍下cocoapods打包和Xcode自己打包的区别
Xocde工程配置
workSpace,project,target,scheme
workSpace
workspace是Xcode的一种文件,用来管理工程和里面的文件,一个workspace可以包含若干个project,甚至可以添加任何你想添加的文件。workspace提供了project和project里面的target之间隐式和显式依赖关系,用来管理和组织工程里面的所有文件。workspace 是以 xcworkspace 的文件形式存在的(这点和 project 一致)。workspace 的存在是为了解决不同 project 之间引用和调用困难的问题。同一个 workspace 下的所有 project 共用同一个编译路径。workspace中的工程默认都是在同一个编译目录下,也就是workspace的编译目录。由于每个工程中的文件都在workspace的编译目录下,所以每个工程之间的文件都是相互可以引用的。因此,如果workspace中的多个工程使用了同一个库的时候,我们就不需要给每个工程都拷贝一个。
project
Xcode中的 project里面包含了所有的源文件,资源文件和构建一个或者多个product的信息。project利用他们去编译我们所需的product,也帮我们组织它们之间的关系。一个project可以包含一个或者多个target。project定义了一些基本的编译设置,每个target都继承了project的默认设置,就是这个参数($(inherited)
),每个target可以通过重新设置target的编译选项来定义自己的特殊编译选项。
一个project可以单独存在,也可以被workspace包含,这种结构就是cocoapods默认我们所看到的,也是现在开发最常用的结构,当你开启pod的时候,会生成一个xcworkspace。
target
target定义了构造一个product所需的文件和编译指令。一个target对应于一个product。target说白了就是告诉编译系统要编译的文件和编译这些源文件的设置说明。编译指令就是根据build settings and build phases
来确定的。
Xcode右上角的侧边栏打开–> Target Membership可以查看 文件归属于哪个Target
target之间可以进行依赖。如果一个target的编译需要另外一个target作为他的输入,那么我们就可以说前者依赖于后者。如果这两个target在同一个workspace里面,Xcode可以发现他们的依赖关系,这种依赖称之为隐式依赖。当然你可以通过设置,明确他们的依赖关系。
- 你可以根据现有的产品,建立不同的Target,右键出来一个Duplicate,然后你修改不同的参数,可以配制出日常打包,线上,预发布等包的配置,通过General,BuildSetting和BuildPhrase来进行定制化修改
- 开发SDK的时候先默认一个Target开发,然后把需要打包的文件新建一个Target进行打包,如果打包Target是后来建立的,可以先remove reference,然后再拖进来,选择对应Target即可
scheme
scheme定义了编译集合中的若干target,编译时的一些设置以及要执行的测试集合。我们可以定义多个scheme,但是每次只能使用其中一个。我们可以设置scheme保存在project中还是workspace中。如果保存在project中,那么任意包含了这个工程的workspace都可以使用。如果保存在workspace中,那么只有这个workspace可以使用。
WorkSpace --- > Project ---> Target
静态库和动态库
库无非就是一种代码共享的方式,根据代码的开源情况,一种开源库,就是我们平时Github上用的那些,另一种就是闭源库,只暴露头文件,看不到具体的实现,是一个已经编译好的二进制文件,比如百度地图SDK,微信SDK等。这个又分两种,一种静态库,另一种动态库。咱们后面的都以我们自己做的静态库为主。
静态库存在形式:
.framework 和 .a
即静态链接库,是一系列从源码编译得到的目标文件的集合,是你的源码的实现所对应的二进制。链接时,静态库会被完整地复制到目标程序中,被多次使用就有多份冗余拷贝。
在 iOS 8 之前,iOS 只支持以静态库的方式来使用第三方的代码。
静态库的优点是,编译完成之后,原始静态库实际上就没有作用了,应用程序没有外部依赖(因为依赖的静态库已经被完整的拷贝进来),直接就可以运行。当然其缺点也很明显,就是会使用应用程序的体积增大。
系统 | 静态库文件 |
---|---|
Windows | .lib |
Linux | .a |
MacOS/iOS | .a |
动态库存在形式
.dylib 和 .framework
一个没有main函数的可执行文件。动态库在链接时并不会被拷贝到目标程序中,目标程序中只会存储指向动态库的引用。等到程序运行时,动态库才会被真正加载进内存,这也是叫做动态库的原因。
动态库的优点是,不影响目标程序的体积,可以随时对库进行升级替换而不需要重新编译。
系统 | 动态库文件 |
---|---|
Windows | .dll |
Linux | .so |
MacOS/iOS | .dylib |
静态Framework,实际上就是.a文件 + .h头文件 + 资源文件的集合,实际上和.a文件的本质是一样的
MacOS/iOS里面的动态Framework
除了上面提到的 .a 和 .dylib/.tbd 之外,Mac OS/iOS 平台还可以使用 Framework。Framework 是一种特殊的文件夹,将库的二进制文件,头文件和有关的资源文件打包到一起,方便管理和分发。
系统的 framework 是存在于系统内部,而不会打包进 app 中。app 的启动的时候会检查所需要的动态框架是否已经加载。像 UIKit 之类的常用系统框架一般已经在内存中,就不需要再次加载,这可以保证 app 启动速度。相比静态库,framework 是自包含的,你不需要关心头文件位置等,使用起来很方便。
在 iOS 8 之前,iOS 平台不支持使用动态 Framework,开发者可以使用的 Framework 只有苹果自家的 UIKit.Framework,Foundation.Framework 等。这种限制可能是出于安全的考虑。换一个角度讲,因为 iOS 应用都是运行在沙盒当中,不同的程序之间不能共享代码,同时动态下载代码又是被苹果明令禁止的,没办法发挥出动态库的优势,实际上动态库也就没有存在的必要了。
iOS 8/Xcode 6 推出之后,iOS 平台添加了动态库的支持,同时 Xcode 6 也原生自带了 Framework 支持(动态和静态都可以)。为什么 iOS 8 要添加动态库的支持?唯一的理由大概就是 App Extension 的出现,可以为一个应用创建插件
。Extension 和 App 是两个分开的可执行文件,同时需要共享代码,这种情况下动态库的支持就是必不可少的了。但是这种动态 Framework 和系统的 UIKit.Framework 还是有很大区别。系统的 Framework 不需要拷贝到目标程序中,我们自己做出来的 Framework 哪怕是动态的,最后也还是要拷贝到 App 沙盒中(App 和 App Extension 的 Bundle 是共享的),因此苹果又把这种 Framework 称为 Embedded Framework。
由于 iOS 的沙盒机制,自己创建的 Framework 和系统 Framework 不同,App 中使用的 Framework 运行在沙盒里,而不是系统中。每个 App 都只能用自己对应签名的动态库,做不到多个 App 使用一个动态库。也就是说,如果不同的 App 使用了同一个动态库 Framework,那该 Framework 会被分别签名、打包和加载。所以,iOS 上我们自己创建的动态库只能是私有的,无法将动态库放置在除了自身沙盒以外的地方。
Xcode SearchPath选项配置
Header Search Paths 和 User Header Search Paths的区别
Header Search Paths来管理导入头文件的路径,这两者的作用是一样的,唯一的区别是在
import
和 include
的时候Header Search Paths会多一种方式
#import "class.h"
#import <class.h>
若在Header Search Paths中设置class的路径后,两种方式都可以。但是如果在User Header Search Pacth中设置后,<>的就会报错。
<>
是从系统空间目录,对应Header Search Paths
中搜索文件," "
从用户空间目录,对应User Header Search Paths
中搜索。如果你把路径加到User Header Search Paths
中,<>
无法从系统目录空间中找到新的路径,从而报错。
一般来讲系统User Header Search Paths是不会有填写的,但默认的User Header Maps
会开启,这个Maps的开关的意思是开启这个开关后,在本地会根据当前目录生成一份文件名和相对路径的映射,依靠这个映射,我们可以直接import工程里的文件,不需要依靠header search path。也就是工程目录下添加的文件默认都会进入映射,不需要指定Header Search Paths。直接默认import " "
即可。
import "xxx.h"的路径搜索顺序:
1. USE_HEADERMAP(如果启用,则会在映射表中查,直接跳过的header search path的配置,如果未查到,则继续往下搜索。)
2. USER_HEADER_SEARCH_PATHS