当我们看好多第三方的插件的时候,会发现好多.a/.framework的静态库,之所以这些大厂这么做,其实是为了方便在集成完毕之后,快速的编译,最重要的是看不到自己的源码,并且保护我们的代码。并且在MRC阶段的代码,如果想要在ARC下使用,那么确实需要进行ARC兼容,但是我们进行编译之后,并不需要考虑这些问题,直接引用这个编译好的静态库就行,并不需要兼容ARC,确实好处多多。
首先讲解如何创建.a的二进制文件
1.创建一个静态库工程
创建静态库2.由于不同的模拟器以及真机对应着不同的架构,
模拟器:4-5:i386 5s-iphoneX:x86-64
真机:3gs-4s: armv7(兼容7s) 5-5c: armv7s 5s-iphoneX:arm64
为了适配所有的CPU架构我们设置如下,Build Active Architecture Only YES表示只编译某些比较主流的架构,所以要想都进行编译,我们需要对所有的都编译
注意如果你用的是比较新的xcode版本,比如xcode10,那么由于并没有自带比较老的模拟器,比如4/4s等,那么生成出来的静态库架构并不会包含i386或者armv7,如果想兼容4/4s等比较久的机型,需要下载老的模拟器版本
设置允许的架构类型3.查看生成的.a文件架构架构
在终端中cd 到.a文件的父文件
输入命令 lipo -info 静态库名称.a
输出当前所有支持的架构
架构类型4.暴露.h 文件
暴露.h5.默认生成的.a文件模拟器和真机是分开的,所以需要合成同时支持真机和模拟器的.a
lipo -create 模拟器中.a文件地址 真机中.a文件地址 -output 新的文件名.a
合成6.生成realease版本的静态库(上面生成的是debug版本)
静态库然后在项目中切换真机/模拟器,command+B,就会在.a文件的根目录发现release/debug、真机/模拟器版本信息
所有的.a文件当我们应用.a文件的时候,只需要将import .h文件进来就可以直接使用静态库文件里的方法了。
接下来我们用framework来生成静态库
.a文件是一个纯二进制文件,.frameWork中除了有二进制文件还有其他的资源文件;.a文件必须有.h文件配合使用,frameWork文件可以直接使用;framework静态库就类似一个文件夹,所有的子类文件都要放到framework中,包括可执行文件frameWork和.h以及资源文件。可以这么总结.a+.h+sourceFile(资源文件)=framework,所以相比.a文件来说,framework更加易于管理,所以本人更倾向与生成framework静态库
同样在新建工程中选择
生成2.项目中添加一个工具类Tool,当Command+B编译之后,在framework中发现只有frameWork.h的头文件,并没有我们想要的Tool.h,所以我们需要对Tool.h暴露,将暴露到外界的.h文件放到public目录下,如果不用frameWork.h主头文件,可以将其删除
暴露.h3.检查可执行文件支持的CPU架构
注意cd到.frameWork,然后检查目录下frameWork可执行文件中的架构,发现是x86_64,由于当前是在模拟器上进行的编译,所以我们需要兼容所有的模拟器/真机类型.
支持的种类修改当前active Architecture Only的类型为No,表示所有的模拟器/真机架构都需要编译
编译架构接下来我们需要在XCode中选择设备处设置为Generic ios Device,编译后的结构可以在项目中的Products->Framework.framework包中找到编译之后的结构,包含真机和模拟器两部分.
选择设备4.适配所有的CPU架构(和上边生成.a文件一样)
5.生成release版本的.framework静态库,而不是debug版本的静态库(注意:当前5到7步是生成了动态库,可以直接看7步中的原因,进行相应的步骤调整。这样写是为了更好的记录遇到的问题)
6.合成适配模拟器和真机的静态库
注意合成的是静态库目录下的可执行文件(frameWork)的合并。
将地址拖过来就行
lipo -create 模拟器中可执行文件地址 真机中可执行文件地址 -output 新的文件名.framework
将生成的frameWork,替换掉之前release版本里的可执行文件,那么这个frameWork就可以用了。
7.修改frameWork(刚生成的时候其实为动态库)为静态库
查看文件 引用framWork防止崩溃的发生,我们需要将frameWork动态库引用进Embedded Binaries
所以,当我们生成frameWork的时候就应该生成静态库。所以在生成frameWork工程中,
修改framework类型为静态库将Dynamic Library修改为 Static Library。所以上边的第5到第7个步骤中我们需要重新进行生成。(删除工程中Library Framework和 Embedded Binaries 中的动态库文件)重新导入工程进行测试,发现不将framework引入Embedded Binaries,也是可以正常运行的,那么现在生成的是静态库了。
记录一些问题:
(1).图片资源的导入问题?(framework和.a图片的导入方式是一样的)
拿生成.a静态库举例,添加资源文件,将图片放入Tool.h同级目录下,编译,会发现我们编译后的包中,并没有我们的资源图片,我们需要将图片文件添加到BuildPhases->CopyFiles中+进来,才会放到我们编译后的文件中,包括三部分.a+.h+资源图片,那么这个时候会出现一个问题:
将我们编译好的.a+.h+资源文件作为一个静态库放入我们的主项目中,如果我们的主项目中已经有一个同名图片,然后对主项目进行编译后,发现包中只有一个同名的图片.
为了解决这个问题,我们将图片放到一个文件夹中,将这个文件夹放到和.a/.h同级目录下,当我们在主工程引用这个静态库的时候,需要将这个文件夹拖入主工程,但是我们用两个选择Create Group/Create folder references,由于我们将静态库导入到主工程,其实包括资源文件/.h/.a都已经编译好了,所以并不需要主工程对当前的资源文件做出引用,应该选择Create folder references,这样会将资源文件/.a/.h都加入到主工程的同级目录下,否则会发现资源文件不见了.
但是如果选择了Create Group,那么就会出问题,所以我们将资源文件放到bundle中进行存放(),bundle中的资源并不会因为这两个选项选错了就会把资源文件位置放错,一直会放到同级目录下.
(2).如果用户导入的头文件过多怎么办?
我们自己先建立一个主头文件,我们将所有的头文件都导入到主头文件中,我们直接import主头文件就行
(3).静态库如何测试?
.a文件如果交给主工程测试无法定位问题所在
所以定义一个复合工程,进行单元测试,在Targets中点击+号,选择Framework & Library 中的CocoaTouch Framework,新建一个Framework工程
添加Testframe复合工程比如我们想要在A业务模块中调试代码,那么我们将A业务模块代码放到主工程中,调用我们想要编译成静态库的代码B,将B(以Tool类为例)
添加到Testframework中在复合工程FrameworkTests中引入我们的静态库类,然后再主工程中调用Tool.h类进行测试,如果没问题,那么在Targets点中我们的复合工程Testframework,配置生成静态库的某些项,编译完之后,再Products中查找Testframework并查看包的内容,生成合并的release静态库.
(4).当我们将组建进行二进制化之后,那么导入方式将会改变,例如#import 'Tool.h' #import <Framework/Tool.h>,如何避免多地方的改动?
我们可以将.h文件剥离出来,独立于framework之外,引用的时候直接调用#import 'Tool.h',实现的时候还是在framework中进行实现
(5).如何将生成的所有的架构文件删除某个架构,该如何操作?比如我们不需要支持i386
第一种:那么我们分别对其他的架构生成静态库,然后将静态库合并
第二种:直接在生成的静态库中移除某一个架构, cd到当前静态库的上一级,命令
lipo -remove i386 文件地址.a -output 生成的名字.a
如果只要某一个架构的文件 比如arm64:
lipo -thin arm64 文件地址.a -output 生成名.a
接下来我们将framework放到远端仓库
以之前创建的一个WKDownLoad私有库为例首先创建一个Framework 项目WKDownloadLb,将WKDownLoad中的代码放到WKDownloadLb中,并暂时删除自带的WKDownloadLb.h文件,Xcode配置相应的生成framework的某些项,mach-o-Type/Build Active Architecture Only/run->release
生成的framwork项目接下来,为了能够方便的查找我们生成framework位置,需要对xcode进行配置File->Project Settings
Project Settings 具体存放位置将framework存放到相对于workspace的同级目录下的products文件下,开始进行编译,然后将framework进行合并,合并完之后,我们可以将WKDownloadLb.framework放到Products中,把iphoneos/iphonesimulator删掉
编译后的路径接下来将原来的WKDownLoad中的podspec文件拷贝一份放到我们的WKDownloadLb项目中,用于进行pod时的查找,并修改podspec,包括库的名称/homepage/source/,在初始化coding.net一个项目的时候,不要设置git.ignore,将coding.net中的homepage/source分别放到podspec对应位置
为了外界不用改动引入我们.h文件的方式,我们做一层兼容,在framework外把所有的.h文件都加进来(真实情况是将所有.h文件都加都一个文件中,只引用某一个文件就行),在根目录下创建一个文件夹,把所有的.h文件都拷贝一份放到这里.
.h的header文件夹修改source_files/s.vendored_frameworks,其中vendored_frameworks其实是指明我们framework的地址专门存在的,由于已经将framework放到了products中,所以地址更短了;source_files是除了framework之外的而外信息,就是我们的暴露header
framework/header设置修改完之后,按照
git add .
git status
git commit -m '标记'
git remote
git remote add origin 地址
git push origin master
git tag -a 'tag值' -m '打标签'
git push --tags
pod lib lint
(1)第一种错误
pod lib lint 第一种错误发现好多小伙伴报这种错误,
解决方案:
pod repo push 私有spec xxxxxx.podspec --allow-warnings --skip-import-validationpod spec lint xxxxxx.podspec --skip-import-validation
这个问题困扰了我很久,终于解决了,感谢小伙伴的贡献啊.
(2)第二种错误
pod lib lint 第二种错误是由于tag没提交远端或者podspec中tag不一致导致的.
最终方案
我们现在远程仓库有一个原生代码组件和一个静态库组件,那么我们这样有一个弊端,需要改两个组件库才能将两个库中代码修改完毕,并且需要进行pod文件名称的修改,很不方便
能不能合并到一个里边那?答案是肯定滴
再原来pod lib create 创建的私有库demo中创建一个framework的复合工程
复合工程将framework复合工程中的部分中引入对原来类的引用,注意只是引用,并不拷贝,这样就能保证修改的是一份代码.
引用生成的framework复合工程部分,引用完之后代码结构
代码结构为了生成静态库,需要在复合项目的build Setting工程目录下修改相应的配置,release/mach-o/build avtive/修改暴露外界的.h到Public中等,然后编译生成静态framework库,合并真机和模拟器的可执行文件,放到原来组件的Classes的同级目录下
products同级目录下接下来,修改podspec文件,并设置静态库暴露在外的头文件以及framework静态库
设置原来组件的podspec修改暴露的source_files为暴露classes中的.h,添加引用的framework地址,这样就算设置完毕了.
接下来我们将修改后的组件上传到原来未二进制化时的私有库中
进行上传...
在其他地方调用本组件,会发现已经二进制化了😆,搞定
最终将上传到私有库的framework进行pod install,但是报了一个错误
pod install发现是由于在pod file中打开了use_frameworks!,注掉就行
我们可以发现:这样并不需要添加一个新的pod依赖,并且通过pod update --no-repo-update就可以更新我们的远端私有库,又节省了远端私有库的资源
最后兼容源码和静态库两种类型
pod 中是可以设置环境变量的,比如 xxx pod install,xxx就是我们的环境变量
安装的时候 IS_SOURCE=1 pod install,就会把原生代码加进来了
兼容原生和二进制注意的地方:
1.如果我们生成的是.a的静态库,并不是framework静态库,那么需要将s.vendored_framework换成s.vendored_libraries就行了
2.如果原来是framework,再IS_SOURCE=1 pod install到原生,那么会发现安装进来的私有库.m文件找不到了,这时需要先将之前的缓存清掉 pod cache clean --all,再将pods文件夹全部删除,这个时候再执行IS_SOURCE=1 pod install
3.如果我们的私有库都用这个环境变量控制,就能起到统一管理私有库源码或者二进制文件导入,但是这个时候有一个问题,如何只设置某一个为原生代码?其他都为二进制
可以给每一个私有库单独设置一个变量,表示当前私有库需要设置二进制 - WKDownload
单独设置为原生执行WKDownload=1 pod install ,这样,私有库中的所有代码都设置为二进制形式,除了WKDownload为原生
4.私有库中有依赖,那么怎么处理
不应该包含依赖的xxx.a/framework文件
并且比如包含很多subspec,那么我们需要将所有的都放到一个framework中
快速生成framework
最后,为了方便,我们很多时候可以利用cocoapods-packager来快速完成framework打包,但是必须注意,我们需要将原来的源码已经用上传到托管平台,并且已加了tag。
1.安装package
安装2.根据生成静态库
静态库生成3.查看支持的类型
支持的类型4.把不需要的类型删除
删除根据路径下的库中的podspec文件,生成相对应的framework,并且支持所有的机型,个人比较倾向于这个工具。
包含subspec的静态库的封装
如果有framework里有依赖,那么我们不能用自动化的cocoapods packager打包framework框架里.
组合项目里创建target framework来进行静态库的封装,打包完之后其实是不包含我们的第三方依赖的,
我们的复合项目中想要用第三方的框架中的代码,那么需要将我们创建的target放入pod中,根据pod 文件的修改
pod 文件添加framework类修改podspec 去掉之前色subspec,将所有的代码看成一个整体
修改podspec 文件里添加source_files/vendored_frameworks
去掉 库名称.dependency '依赖库名称'
然后按照framwork的封装进行提交到私有库
欢迎关注我的公众号,专注iOS开发、大前端开发、跨平台技术分享。
iOS开发之家
网友评论