XCode打framework包、cocoapods库制作及Pods库的二进制切换

近来公司的公共库里有点小问题,但是公共库打成了framework,即使手上有源码也很难调试。网上百度了很多方法,有临时方法,也有比较好的方案,写一篇博客记录下来,送给正在调试framework的你,哈哈哈。

所以呢,这篇文章中你会看到:

  • 调试framework的临时方案
  • framework的制作方式
  • 推送库到cocoapods
  • Pods库的二进制切换

调试framework的临时方案

这个方案呢,只能作为临时方案,因为这个方案还是有一定局限性的。先说实现方式吧。

首先呢,framework的库调试的痛苦在于第一你打不了断点,第二你也看不到堆栈信息。

所以从两方面入手逐个击破就好。 首先如果你有源码的话,只要打开你源码的工程build一下,你就会得到一个framework文件。

  • 看不到堆栈信息 这是因为framework打包的时候不在你的电脑上,所以你的电脑并没有framework对应的符号表,你就看不到信息了,所以很简单的方式就是右键show in finder拿到framework然后替换你主工程中响应的framework就好了,这时候你已经可以看到堆栈信息了。

framework.png

  • 无法打断点

至于打断点这个就比较玄幻了。首先打开你的主工程,然后从你framework的源码中把你要打断点的.m文件拖到主工程里下图的位置。为打开文件但不会引入文件,这个时候你打下断点试试你就会发现神奇的居然进入了断点。

断点

但是这种方式的缺点是,xCode关了你就还需要重新弄一遍,而且提交代码前要记得把framework替换回去。所以再请教了一个大神之后,他告诉我一个二进制切换方案,自己试了下很好用。由于这种实验我不可能那公司的公共库做实验,所以就完全自己从framework制作开始走了一遍流程。这里都记录一下。


framework的制作方式

1.framework制作在新版本的xcode上已经十分简单了。首先创建工程的时候选择Cocoa Touch Framework。

01.jpg

然后他会自动生成一个头文件的.h,我这里不想重新走一遍流程就偷懒用后面的图了。

02.jpg

2.然后你就开始创建你要打framework的文件就好,在别的地方写好了拖进来也好。然后在自动生成的头文件中引入你想对外暴露的.h文件就好。至此代码层级的事情就完成了。

3.接下来你需要再做3个buildsetting的设置。

03.jpg

4.然后你要设置你对外暴露的文件

04.png

5.接下来,如果你的库只想支持模拟器就选择模拟器build一下,想支持真机就Generic iOS Device build一下。

05.png

6.build成功之后再Products文件夹下则会生成一个framework文件。如果你是只支持某一种平台的话到这里已经结束了,但是如果你要支持真机和模拟器的话,我们还要将framework合并。这里讲一下合并的方法。就是终端输入一段命令:

终端指令:lipo -create +上面两个文件的路径 +-output+ 合成后文件的输出路径

例如我的是这个样子的:

06.png

这是将刚刚生成的文件替换到两个framework中的任意一个,然后被替换的framework就是支持两种平台的了。

07.png

更为详细的教程你可以看这里,《Xcode9.0 制作.framework》


推送库到cocoapods

我说一个我做库的目录结构吧。 一般情况下我会这样,建一个根目录A,然后根目录下存放两个文件夹,一个叫Demo,一个叫你的库的名字,如DWFlashFlow。然后Demo里面放一个你库的使用Demo就好,以库命名的文件夹里面存放库文件。根目录下存放spec,最后连同根目录一起传到一个远程仓库中。

结构

文件编辑好了我们来编辑一下PodSpec。

Pods为我们提供了很多可选项,让你有丰富的定制可能,这里我说一下我常用的及必须的几个选项。我发一下我的config,你可以直接做模板改就好了

Pod::Spec.new do |s|
s.name = 'DWFlashFlow'
s.version = '1.0.1'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.summary = '网络请求库,核心基于AFN3.0,实现批量请求、链请求及依赖请求。Network request library, core based on AFN3.0, implements batch request, chain request and dependency request.'
s.homepage = 'https://github.com/CodeWicky/DWFlashFlow'
s.authors = { 'codeWicky' => 'codewicky@163.com' }
s.source = { :git => 'https://github.com/CodeWicky/DWFlashFlow.git', :tag => s.version.to_s }
s.requires_arc = true
s.ios.deployment_target = '7.0'
s.source_files = 'DWFlashFlow/**/{DWFlashFlow,DWFlashFlowManager,DWFlashFlowBaseLinker,DWFlashFlowAFNLinker,DWFlashFlowAbstractRequest,DWFlashFlowRequest,DWFlashFlowBatchRequest,DWFlashFlowChainRequest,DWFlashFlowCache}.{h,m}'
s.frameworks = 'UIKit'
s.dependency 'DWNetworkAFNManager', '~> 1.0.0'
end

对照着在Pod search时看到的字段有些属性你自然就懂了。

字段

这里着重说一下source_files这个字段的规则。

DWFlashFlow/**/{DWFlashFlow,DWFlashFlowManager,DWFlashFlowBaseLinker,DWFlashFlowAFNLinker,DWFlashFlowAbstractRequest,DWFlashFlowRequest,DWFlashFlowBatchRequest,DWFlashFlowChainRequest,DWFlashFlowCache}.{h,m}

首先路径是相对于PodSpec所在的目录的,因为我们将Spec放在了根目录下,并且根目录下的DWFlashFlow文件夹下方的是我们的库文件,所以第一个目录我写了一个DWFlashFlow,第二个路径是两个星号,这代表搜索方位将是当前文件夹下的所有文件(包括子文件夹中的文件)。前两个路径就声明了当前库文件的搜索范围就是根目录下DWFlashFlow中的所有文件。接下来在大括号之间的内容就是我们库文件的文件名在这些字符串间选择,然后后面的大括号之间是库文件的扩展名在这之间选择,通过这个路径,我们就确认了所有库文件的文件名。

另一个字段是source:

s.source = { :git => 'https://github.com/CodeWicky/DWFlashFlow.git', :tag => s.version.to_s }

分成两部分,前面一部分是告诉pods去这个地址拉取文件,当然就是填你远端的仓库地址啦,后面的tag就是告诉pods你要拉取的版本。这里你如果就像我这么写的话,就是取s.version的值了。

Spec文件是pod识别库的唯一文件,制作好了我们就要开始上传了。

1.首先如果你要发布的版本是0.0.1版本的话就给当前库打一个0.0.1的tag。然后推到远端。

2.spec中version改为0.0.1。

2.5 这时如果你是第一次制作的话你还要注册一下。

执行命令pod trunk register 邮箱地址 ‘用户名’ –verbose

3.本地校验一下库的合法性

cd到库的根目录,然后终端执行pod spec lint XXX.podspec 这一步会报warning和error,根据信息去修改就好了,如果你想忽略警告的话,命令后面记得加 --allow-warnings(第一个是两个短横线,第二个是一个)

4.上传库

终端执行pod trunk push XXX.podspec

然后你就可以开始等待了,当出现这个页面时就是上传成功了。

我没有图,copy一个。

成功

成功以后你就可以pod search 一下啦。如果你是第一次发布当前库的话,你要执行清除索引命令,因为索引是在上一次没有索引的情况下调用search生成的,里面不会有你的新库的信息,所以要清除旧的索引。

rm ~/Library/Caches/CocoaPods/search_index.json

并且此时执行下pod repo update来更新一下仓库,再执行search就会搜索到你的库了,如果没搜到,那就是你弄错了。

更详细的公共库制作方式你可以看这里,《将代码提交到CocoaPods超详细的操作步骤和图解

上面的步骤告诉了你如何上传至cocoapods的公共库,接下来我会再说一下上传到你的私有库的方法。

首先打开到.cocoapods/repos目录下。

Repos

正常的话如果你没有私有库的话,你应该只有一个master文件夹。

在你的远端仓库中创建一个私有仓库,叫什么随你便了,复制一下仓库地址。

然后执行比如说你私有库的名字是REPO_NAME,远程仓库地址是SOURCE_URL,就执行命令

pod repo add REPO_NAME SOURCE_URL

这时候repos目录下应该会多一个你刚才创建仓库名的文件夹。 这里要解释一下,你刚刚创建的仓库只是作为私有库版本管理库存在,他应该是一个空的仓库,而不是你要做私有库的那个地址,希望你清楚。

然后你有了私有仓库,spec文件跟共有库是一样的,只是推得时候命令不一样,这时候你要用的命令是

pod repo push REPO_NAME xxx.podspec

其他都一样,这时候你已经可以通过pod search 搜索到你的私有库了。

不过当你需要安装库的时候你的podfile还需要做一定的改动,就是要告诉pod你的仓库的实际地址。

也就是说你的podfile大概是这个样子的:

source 'https://github.com/CocoaPods/Specs.git'
source 'SOURCE_URL'

target 'TestWork' do
pod 'Test_Frm_Private', '~> 0.0.1'
end

至此你也可以安装私有库了。

如果你想看到更详细的私有库推送,你可以看这里,《如何创建私有 CocoaPods 仓库》


Pods库的二进制切换

先说很重要的一个,二进制切换只支持私有库,这是大前提,一会解释原因。

终于走到最后一个流程了,其实podSpec是支持条件语句的,比如说这样:

Pod::Spec.new do |s|
s.name = 'Test_Frm_Private'
s.version = '0.0.1'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.summary = 'A test for pods.'
s.homepage = 'https://coding.net/u/codeWicky/p/Test_Frm_Private'
s.authors = { 'codeWicky' => 'codewicky@163.com' }
s.source = { :git => 'https://git.coding.net/codeWicky/Test_Frm_Private.git', :tag => s.version.to_s }
s.requires_arc = true
s.ios.deployment_target = '7.0'
s.frameworks = 'UIKit'

  $lib = ENV['use_lib']
  if $lib 
     puts 'Install Test_Frm_Private via Framework'
     s.ios.vendored_framework = "Test_Frm_Private/Framework/Test_Frm_Prvt.framework"
  else
     puts 'Install Test_Frm_Private via SourceCode'
     s.source_files = 'Test_Frm_Private/SourceCode/**/{Test_Frm_Prvt,Test_Frm_Prvt_Handler}.{h,m}'
  end
  
s.preserve_paths = "Test_Frm_Private/**/*"
end

如上,当Podspec是上面这个样子的时候我们可以通过use_lib=1 pod install让他命中if的第一个分支。原理大概就是pods会把pod前的所有字段作为一个字典供podSpec使用。既然有了条件分支,我们的目标就是根据不同条件改变pods的不同资源了。呐Spec的代码也比较清晰,没什么多余说的,只是framework资源的引入跟.h.m的图引入是有一点小小的区别的,就是对应的字段不一样。注意一下就好了。

那为什么说只有私有库能做这种切换呢?是因为只有podspec支持条件语句,但是当你传到共有库时podspec会被转换成podspec.json。目的也很清楚,开发者既然已经封装成framework了就一定不想让你看到.h.m,所以这个接口就没有给公共库留出来,只有私有库可以。

至此,你就可以通过use_lib=1 pod install加载framework形式的库,pod install加载.h.m的库了。

如果你想看更多关于二进制切换的内容,来这里吧《Pod二进制化


好吧,为了追查framework中的一个问题,我饶了如此大一圈,你可能会说既然有缘吗为什么不直接引入.h.m的形式,原因是事实上framework的编译速度会高于.h.m形式的速度,并且一般较大的公司的库都是很成熟、经历了多个版本的修改的不希望你随意去改变的库,所以封装成framework也很方便管理,所以才有了这方面的需求,那就是这样。我也就当记录一下全过程,免得自己忘了。呐,这次没有广告啦,拜拜。

参考资料:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏好好学java的技术栈

java工程师必备linux常用命令,这篇文章就够了

bash 是一个为GNU计划编写的Unix shell。它的名字是一系列缩写:Bourne-Again SHell — 这是关于Bourne shell(sh)...

2571
来自专栏Laoqi's Linux运维专列

部署Java项目(Ⅰ)

3085
来自专栏阮一峰的网络日志

Systemd 入门教程:命令篇

Systemd 是 Linux 系统工具,用来启动守护进程,已成为大多数发行版的标准配置。 本文介绍它的基本用法,分为上下两篇。今天介绍它的主要命令,下一篇介绍...

3566
来自专栏淡定的博客

php之laravel学习常见错误1(连载中)

下面是我们整理的php的laravel学习的常见的错误以及解决的办法,我还会持续更新,请关注

1733
来自专栏安恒网络空间安全讲武堂

IAT Hook 技术分析

来源:https://pentest.blog/offensive-iat-hooking/

1742
来自专栏散尽浮华

centos6下redis cluster集群部署过程

一般来说,redis主从和mysql主从目的差不多,但redis主从配置很简单,主要在从节点配置文件指定主节点ip和端口,比如:slaveof 192.168....

53210
来自专栏潇涧技术专栏

One Trip of building a Crawler

最近需要从网上抓取大量的数据,于是体验了一下爬虫程序的开发和部署,主要是学会了一些实用工具的操作。

1202
来自专栏JAVA高级架构

Rabbitmq---消息队列

有了消息队列,每一次连接不管是生成消息还是消费消息,都有各自的逻辑与其他逻辑无关--通信解耦

1373
来自专栏北京马哥教育

文件和文件夹的个数是否对磁盘的IO有影响?

在linux和windows的亦或是其他操作系统的 文件系统 中,文件的个数、文件夹的个数、文件夹的层级数是否对磁盘的IO有影响?如果有,那么大概的阈值是多少?...

3216
来自专栏C/C++基础

Linux基础知识点

文件(包括目录)权限分为三类别,从左至右依次是:文件所属主的权限、文件所属所在用户组的权限和其他用户的权限。

1992

扫码关注云+社区

领取腾讯云代金券