前言

三个月没写东西了,是时候总结点东西了。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可以发现他们的依赖关系,这种依赖称之为隐式依赖。当然你可以通过设置,明确他们的依赖关系。

  1. 你可以根据现有的产品,建立不同的Target,右键出来一个Duplicate,然后你修改不同的参数,可以配制出日常打包,线上,预发布等包的配置,通过General,BuildSetting和BuildPhrase来进行定制化修改
  2. 开发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来管理导入头文件的路径,这两者的作用是一样的,唯一的区别是在
importinclude的时候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
3. HEADER_SEARCH_PATHS

import <xxx.h> 的路径搜索顺序:

1. 系统路径
2. USER_HEADER_SEARCH_PATHS (ALWAYS_SEARCH_USER_PATHSYES,则会搜索该路径,该变量默认是NO,并且已经被标记为Deprecated)
3. HEADER_SEARCH_PATHS

系统路径到底是什么路径?
可以在Build Phases -> Link Binary With Libraries添加一些静态库和动态库依赖,然后Show In Finder查看具体路径

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk
这里面有两个System和usr两个查找目录

总结:所以说一般情况下你至少默认配置下,设置好Header Search Paths可以通过" "<>引用。双引号是用于本地工程的头文件,需要指定相对路径,默认在Maps自动开启映射,尖括号是全局的引用(编译器类库搜索路径),想搜哪里直接填路径就好了或者干错直接写死(不建议),其路径由编译器提供,如引用系统的库。但在实际工程里,不仅不用指定相对路径,而且用<>也是能引用到的.

Library 和 Framework Search Paths

这两个是搜索Library和Framework配置的路径

$(SRCROOT) 和 $(PROJECT_DIR)

$(SRCROOT) 和 $(PROJECT_DIR) 都指xxx.xcodeproj所在的父目录

$(inherited)

项目的Framework Search Paths添加$(inherited)参数会从WorkSpace->PROJECT -> Build Settings -> Framework Search Paths里面的路径会被其继承,没有的话不会继承。所以一个项目里面有多个target,使用到了同一个库(Library或Framework)那么为了方便我们可以在target添加继承参数,并且PROJECT统一中添加库的路径。

Other Link Flags

Other Linker Flags中加入-ObjC或者-all_load或者-force_load
这个最常见的就是-Objc,一般导入第三方的时候或者你做好了一个SDK,自己测试的时候,导入Demo工程,发现直接崩溃

selector not recognized

这里你可以通过ar -x libxxx.a文件查看SORTED文件,默认是没有Category被链接的,所有你跑起来就会找不到,从而崩溃,具体后面在分析。
-ObjC
一般这个参数足够解决前面提到的问题,这个flag告诉链接器把库中定义的Objective-C类和Category都加载进来。这样编译之后的app会变大,因为加载了很多不必要的文件而导致可执行文件变大。但是如果静态库中有类和category的话只有加入这个flag才行,但是Objc也不是万能的,当静态库中只有分类而没有类的时候,Objc就失效了,这就需要使用-all_load或者-force_load了。
-all_load
-all_load会强制链接器把目标文件都加载进来,即使没有objc代码。但是这个参数也有一个弊端,那就是你使用了不止一个静态库文件,那么你很有可能会遇到ld: duplicate symbol错误,因为不同的库文件里面可能会有相同的目标文件 这里会有两种方法解决 1:用命令行就行拆包. 2:就是用下面的这个参数
-force_load
这个flag所做的事情跟-all_load其实是一样的,只是-force_load需要指定要进行全部加载的库文件的路径,这样的话,你就只是完全加载了一个库文件,不影响其余库文件的按需加载 .
注意:有时候做了SDK,弄了个Demo测试SDK,用Pod引入第三方依赖,但是怎么就报错第三方找不到呢,注意看Other Link Flags这个参数,是否有-l的引用,或者$(inherited)时候有配置,不然是不会被link到的

Cocoapods原理

  1. 避免直接导入文件的原始方式,方便后续代码升级 (版本升级)
  2. 简化、自动化集成流程,避免不必要的配置 (ObjC问题和Search Path配置)
  3. 自动处理库的依赖关系 (Dependency依赖)
  4. 简化开发者发布代码流程 (Pod Package,发布到仓库Spec管理)

最简单的静态库制作验证Other Link Flags问题

.a.Framework的区别上面介绍了,我们直接做.a


















































打开Xcode









File









New









Project









Cocoa Touch Static Library







然后你随便加个Categroy验证下-objc这个加载,现在暂时不管什么编译架构的问题,直接按住,Command + B,跑起来,MKJFirstStaticLibrary 项目下现在有两个类,一个是MKJFirstStaticLibrary,一个MKJFirstStaticLibrary+Extension,你就能得到一个.a文件。

What’s Fuck .a 文件????
这货就是一个压缩文件而已,压缩了所.o目标文件的集合。

ar -x libMKJFirstStaticLibrary.a


这里默认编译的时候已经在 Build Phases Copy Files添加了我们需要暴露的头文件。
再新建一个Project壳(mainProject工程),把.a和.h文件拖进去,运行我们刚才写的代码,这个时候你就能看到经典的问题。

-[MKJFirstStaticLibrary sayEx]: unrecognized selector sent to instance 0x6000036dbbe0
2019-03-24 14:36:58.252513+0800 OtherLinkFlags[35181:326759] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MKJFirstStaticLibrary sayEx]: unrecognized selector sent to instance 0x6000036dbbe0'

首先,我们知道.a其实是编译好的目标文件的集合,因此问题出现在链接这一步,而非编译。OC在使用静态库的时候,需要知道哪些文件需要链接进来,它依据的就是上面ar -x解压出来的__.SYMDEF SORTED文件。
这个文件不会包含所有的 .o 目标文件,而只是包含了定义了类的目标文件。我们可以执行cat __.SYMDEF\ SORTED 来验证一下,你会看到其中并没有拓展类的信息。这样一来,MKJFirstStaticLibrary+Extension虽然存在,但是不被链接到最终的可执行文件中,从而导致了找不到方法的错误。
解决上述问题的方法是调用者在 Build Settings 中找到 other linker flag,并写上-ObjC 选项,这个选项会链接所有的目标文件。然而根据文档描述,如果静态库只有分类,而没有类, 即使加了-ObjC 选项也会报错,应该使用 -force_load 参数。这几个参数上面有介绍。
因此这些琐碎的东西Cocoapods给我们默认加上了,但是如果你自己做SDK自己搞得话总归会遇到那些本该就遇到的坑,虽然现在别人帮你做了,但是出来混,总是要还的。。。所以,了解下原理很有必要。

总结:

上面我们的做法是把编译好的静态库拖进去,这样明显就有了显示依赖,那Cocopods为何就不需要?打开你的Cocoapods项目,你会注意到下面几个问题:

  1. 主工程中没有导入第三方库的代码或静态库
  2. 主工程不显式的依赖各个第三方库,但是引用了 libPods-项目名.a 这个 Cocoapods 库,如果是Framework就是Pods_项目名.framework,你可以看下Link Binary With Librarys
  3. 不需要手动编译第三方库,直接运行主工程即可,隐式指定了编译顺序

以上的做法就是cocoapods的做法,但是你必须开启xcworkSpace来打开工程。

做个简单的Demo模拟下Cocoapods引用结构

1.新建一个主工程mainProject
2.然后在主工程下新建一个Pods文件,在里面新建Project,用静态库做Target
结构如下,mianProject是编写的主工程,Pods里面是静态库的Target

其中Pods,FirstLibrary和SecondLibrary是三个不同的Target,其中Pods什么都不做,只是依赖其他两个,这个就是Cocoapods项目下的做法,先看下Cocoapods的图解

这里是静态库,那么Cocoapods会给我们创建一个总的静态库Pods-项目名规则命名,该库什么都不做,只是依赖其他第三方库,然后我们在看看主工程要做什么。

首先主工程下面我们需要在Link Binary With Librarys里面添加那个主的静态库,Pods-xxx.a,然后再Other Link Flag里面告诉Xcode我们需要在Xcode里面引用哪些静态库和Framework。
这样,我们跑主工程的时候,编译的时候会先去依赖Pods-xxx.a,然后这个总的Pods会把依赖的其他第三方静态库编译好,这样在最后链接的时候都能找到,并且成功链接成可执行文件。

3.模拟Cocoapods
Pods下面新建一个工程,放入三个静态库,Pod作为总的依赖其他两个

这个就是Pods子工程了,理论上Pods静态库是给外部引用的,那么内部它依赖的其他第三方库,因此按照Cocoapods,需要给这个Pods设置Header Search PathLibrary Search Path

// 设置成recursive 默认不会递归查找 Header Search Path
"${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/include"// Library Search Path 设置
"${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/FirstLibrary"
"${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/SecondLibrary"

这几个参数是参考Cocoapods的,环境变量的值可以参考如下,一个简单的静态库编译后.a文件的目录就是按这个查找的
Xcode环境变量详细罗列
第三步总结

  1. 新建一个文件夹创建静态库Projects
  2. 创建多个静态库Target,统一创建一个Pods来管理,什么都不做,只设置依赖
  3. Project–>Pods Target下设置Header Search Path 和 Library Search Path
  4. 第三步在后续测试中竟然可以不设置,亲测Cocoapods项目下删掉这两个也可以,但是前提是你在主工程要配置好,所以这里的两个Search有点看不懂了,具体的理论介绍可以看上面,但是这么可能是在同一个workSpace下默认已经能找到了。

4.主工程配置









































Target









Build Phases









Link Binary With Librares









Pods.a

































































Target









Build Setting









Other Link Flags









-Objc









-lFirstLibrary









-lSecondLibrary







第一条分支告诉Xcode Link的时候需要依赖Pods,这个Pods自己的依赖会优先被编译出来。
第二条分支告诉Xcode实际需要使用的静态库,-l "xxxxxx"
然后在主工程import <头文件>测试子工程下面的两个静态库

以上做完之后,如果你没有合理的设置Other Link Flags就会报错,如果少了-ObjC就会报上面介绍的找不到方法的那个错误,如果链接错误或者路径错了,就会报如下

一般只要设置好Link的参数,要么是Search Path,要么是Other Link Flags,否则就会有Linker方面的错误。

下一篇传送门

以上介绍了基本的一些参数配置,下一篇详细介绍下Cocoapods的配置以及Xcode环境变量的介绍
Demo地址

参考如下:
Objc中Import的介绍
Cocoapods
Xcode Help
<> 和 ""的搜索路径

iOS SDK开发系列一之Cocoapods原理,Xcode的配置,动态库和静态库介绍相关推荐

  1. iOS SDK开发系列三之微信支付SDK封装Demo以及xcodebuild简单打包脚本实现

    前言 SDK开发系列文章一 SDK开发系列文章二 之前两个文章介绍了Cocoapods的原理,Xcode环境变量以及动态库和静态库的介绍,这些基本知识就单独抽出来了,方便以后翻阅.看了一遍网上的一些静 ...

  2. 移动开发在路上-- IOS移动开发系列 多线程二

    最近太忙没太多的时间,忙碌的码农生活空下来一点时间,都会挤出来看一些技术或者咨询的文章,废话不多说,直奔主题. 接着上一次的继续说. 定时器在多线程的使用 NSRunLoop 是线程相关的基础框架的一 ...

  3. ios助手开发系列(一):需求分析以及技术调查

    目录: ios助手开发系列(一):需求分析以及技术调查 ios助手开发系列(二):第一步,设备连接 买过苹果设备的人基本上都用过苹果官方自带的iTunes吧,别提有多难用了,使用习惯和我们完全不同.. ...

  4. ios 开发中 动态库 与静态库的区别

    使用静态库的好处 1,模块化,分工合作 2,避免少量改动经常导致大量的重复编译连接 3,也可以重用,注意不是共享使用 动态库使用有如下好处: 1使用动态库,可以将最终可执行文件体积缩小 2使用动态库, ...

  5. 细聊 Cocoapods 与 Xcode 工程配置

    前言 文章比较长,所以在文章的开头我打算简单介绍一下这篇文章将要讲述的内容,读者可以选择通篇细度,也可以直接找到自己感兴趣的部分. 既然是谈 Cocoapods,那首先要搞明白它出现的背景.有经验的开 ...

  6. 【Android NDK 开发】Android.mk 配置静态库 ( Android Studio 配置静态库 | 配置动态库与静态库区别 | 动态库与静态库打包对比 )

    文章目录 I . Android Studio 中使用 Android.mk 配置静态库 总结 II . 第三方动态库来源 III . 配置 Android.mk 构建脚本路径 IV . 预编译 第三 ...

  7. 【Android NDK 开发】Android Studio 使用 CMake 导入静态库 ( CMake 简介 | 构建脚本路径配置 | 引入静态库 | 指定静态库路径 | 链接动态库 )

    文章目录 I . CMake 简介 II . Android Studio 中 CMake 引入静态库流程 III . 指定 CMake 最小版本号 IV . 导入函数库 ( 静态库 / 动态库 ) ...

  8. 基于Swift利用百度lbs定位iOS SDK开发遇到的问题汇总

    在使用Swift语言,利用百度地图开放平台的iOS定位SDK进行开发时,可能会遇到一些小问题.基于一些笔者在开发过程遇到的问题,本文对问题进行了总结,并且给出了相应的解决方案供各位入门的开发者参考. ...

  9. 涂鸦 Wi-Fi SDK开发系列教程——5.对模组二次开发

    上期精彩回顾:Wi-Fi模组二次开发课程--4.烧录授权 本章节旨在通过一个简单Demo,使开发者能够了解涂鸦SDK的启动流程,带领开发者基于涂鸦提供的SDK对模组进行二次开发. Demo功能介绍:硬 ...

最新文章

  1. BZOJ2062 : 素颜2(face2)
  2. Unbutu下安装mysql服务并允许远程登录
  3. python 协程可以嵌套协程吗_Python线程、协程探究(2)——揭开协程的神秘面纱...
  4. 电脑硬件知识学习_编程入门书籍:大学学习计算机基础必读 5 本经典入门书籍,收藏...
  5. Python os.chdir() 方法
  6. 庄河投资10亿元强力打造“智慧城市”
  7. Android版MrHuo工作室客户端开发心得(二)
  8. 小明利用计算机软件绘制函数,辽宁省大连市2014年高二学业水平模拟考试 信息技术试题(三)...
  9. 对break和continue的一些个人认知(称不上见解)
  10. 判断URL的HTTP状态
  11. anchor-free目标检测
  12. 离散数学3_第1章__一些重要的重言蕴涵式__推理定律
  13. 区块链是什么通俗解释_区块链技术是什么?区块链的通俗解释原来是这样!
  14. DTI脑影像处理流程-配准机器生成的特征图
  15. pics_IE的内容顾问,PICS评级和当今的ASP.NET Flakey
  16. 重定向(Redirect)和请求转发(forward)的区别?
  17. 用Cool Edit Pro 2.1做铃声渐入的效果
  18. GO+ 教程总览(二)
  19. 【安全】5招防御偷窥软件
  20. 档案馆库房环境温湿度空气质量等相关要求

热门文章

  1. git报错 failed: The TLS connection was non-properly terminated
  2. 7-3 降价提醒机器人 (10 分)小 T 想买一个玩具很久了,但价格有些高,他打算等便宜些再买。但天天盯着购物网站很麻烦,请你帮小 T 写一个降价提醒机器人,当玩具的当前价格比他设定的价格便宜时发
  3. openpyxl修改图表标题字体和字号
  4. python random模块中seed函数的详解_random.seed()函数理解
  5. c++空指针的定义-0L
  6. RFID技术对危化品实行安全化
  7. 飞腾CPU体系结构之位序
  8. Java EE系列(九)——Java EE连接Mysql数据库(JDBC保姆级教学)
  9. 59深度解密五十九:利用“抖音”进行吸粉的简单、另类玩法
  10. 玩转Python量化金融工具之NumPy