@qidiandasheng
2020-10-09T15:04:49.000000Z
字数 7187
阅读 15158
使用工具
二进制化指的是通过编译把组件的源码转换成静态库或动态库,以提高该组件在App项目中的编译速度。我们现在项目中除了第三方库,还有自己的很多业务组件都集成到了pod里面。
用ls -lR| grep "^-" | wc -l
查看了Pods
目录里的所有文件差不多有5000个文件。而每次pod update
的时候CocoaPods
都会在你build
的时候生成对应的target
,然后编译成.a静态库或者.framework动态库。这样会消耗大量时间,如果简单的pod update
大概花费了3分钟,如果build clear
的话是5分钟。
所以我们二进制化的话就节省了很多源文件的编译时间,直接使用编译好的二进制文件。
1、 第三方库会被编译成.a静态库或.framework动态库供我们真正的工程使用。CocoaPods
会将所有的第三方库以target的方式组成一个名为Pods的工程。如果podspecs
里是以s.resource_bundles
的方式导入资源文件的话会有一个对应bundle的target,一般这个target命名就是podName-bundleName
。最后这个Pods工程就放在刚才新生成的Pods目录下。
整个第三方库工程会生成一个名称为libPods-projectNmae.a
的静态库或Pods_projectNmae.framework
的静态库提供给我们自己的主工程使用(projectNmae为主工程名字)。我们能在主工程的target的General -> Linked Frameworks and Libraries
里看到这个Pods工程生成的静态库。
对于资源文件,CocoaPods提供了一个名为Pods-resources.sh的bash脚本,该脚本在每次项目编译的时候都会执行,将第三方库的各种资源文件复制到目标目录中。我们能在主工程的target下的Build Phases -> Copy Pods Resources
看到脚本执行Pods-resources.sh
。
2、 我们的工程和第三方库所在的工程会由一个新生成的workspace管理
为了方便我们直观的管理工程和第三方库,主工程和Pods工程会被以workspace的形式组织和管理,也就是我们看到的projectName.xcworkspace
文件。
3、 原来的工程设置已经被更改了,这时候我们直接打开原来的工程文件去编译就会报错,只能使用新生成的workspace来进行项目管理。
4、 CocoaPods通过一个名为Pods-ProjectName.debug.xcconfig
的文件来在编译时设置所有的依赖和参数(这里的debug可以是release
或schemeName
)。
在Pods -> Target Support Files -> Pods-ProjectName
目录下可以看到。
5、 下面是CocoaPods
源码sandbox.rb
注释里的一个关于生成的Pods
目录结构。
# Pods
# |
# +-- Headers
# | +-- Private
# | | +-- [Pod Name]
# | +-- Public
# | +-- [Pod Name]
# |
# +-- Local Podspecs
# | +-- External Sources
# | +-- Normal Sources
# |
# +-- Target Support Files
# | +-- [Target Name]
# | +-- Pods-acknowledgements.markdown
# | +-- Pods-acknowledgements.plist
# | +-- Pods-dummy.m
# | +-- Pods-prefix.pch
# | +-- Pods.xcconfig
# |
# +-- [Pod Name]
# |
# +-- Manifest.lock
# |
# +-- Pods.xcodeproj
关于二进制化之后头文件的导入问题,一般我们会生成Framework,而Framework里的头文件导入会使用@import
或#import <>
,那么要在不改变原有#import ""
方式导入头文件的情况下使用Framework该怎么办呢?可以参考:iOS里的导入头文件。
其实我们生成的Framework是有静态库和动态库两种的,关于这两种的使用方式也是不一样的,关于Framework静态库和动态库的区别设置,还有Framework资源的读取方式可以参考iOS里的动态库和静态库。
终端输入lipo -info
可以查看静态库或动态库支持的动态库架构。lipo -info name.a
或lipo -info name.framework/name
。
为了编译快我们可能会使用二进制,但是有时候我们需要切换回源码进行调试代码。我们在pod仓库中同时存放源码和生成的二进制库,然后就是让CocoaPods
在安装Pod的时候判断使用源码还是二进制库。可以使用以下代码:
$lib = ENV['use_lib']
$lib_name = ENV["#{s.name}_use_lib"]
if $lib || $lib_name
puts '---------binary-------'
s.ios.vendored_framework = "Framework/#{s.version}/#{s.name}.framework"
//这种是帮你打包成bundle
s.resource_bundles = {
"#{s.name}" => ["#{s.name}/Assets/*.{png,xib,plist}"]
}
//这种是你已经打包好了bundle,推荐这种,可以省去每次pod帮你生成bundle的时间
s.resources = "#{s.name}/Assets/*.bundle"
else
puts '.......source........'
s.source_files = "#{s.name}/Classes/**/*"
s.resources = "#{s.name}/Assets/*.bundle"
s.public_header_files = "#{s.name}/Classes/**/*.h"
end
使用use_lib=1 pod install
安装二进制库,pod install
安装源码库。
这里我们在切换的时候发现两个问题:
切换为另一种格式的时候发现Pod目录下为空,没有需要的文件。
这是因为你安装的时候告诉了cocoapods
安装源码还是二进制库,在第一次安装的时候先安装的是哪一种,那么~/资源库/Caches/CocoaPods/
这个缓存里就只有那一种的文件,另一种就根本没下载。而在切换的时候cocoapods
会去缓存里读另一种格式的文件,然而缓存里并没有,所以Pod目录下面就会为空。
解决方法:那么我们就需要cocoapods
下载到缓存的时候两种格式的文件都下载,包括资源文件。我们在Podspec里加入以下代码,告诉pod需要下载哪些文件。
s.preserve_paths = "#{s.name}/Classes/**/*","#{s.name}/Assets/*.{png,xib,plist}","Framework/#{s.version}/#{s.name}.framework"
还有就是资源文件读取的问题
一般我们生成的Framework里面都会包含了资源文件,而以上代码中的use_lib=1
的时候使用了s.resource_bundles = {}
,表示在main bundle
也引入了资源文件。那么可能就会存在两份相同的资源文件。那么这里就会有两种方案:
1、使用main bundle
里的资源文件(推荐)
前提:
use_lib=1
的时候使用s.resource_bundles = {}
把资源导入main bundle
中。删除Framework里的资源文件。优点:
保持了现有代码[NSBundle mainBundle]
读取main bundle
里的资源的方式,不用修改源码。缺点:
如果Podfile
里加入use_frameworks!
,use_lib=0
使用源码,cocoapods
会自动把资源帮你打包进它编译好的Framework里,就不能保持现有代码[NSBundle mainBundle]
读取main bundle
里的资源的方式。只能使用bundleForClass:
读取Framework里的资源的方式。
2、使用Framework里的资源文件
前提:
use_lib=1
的时候不使用s.resource_bundles = {}
把资源导入main bundle
中。保留Framework里的资源文件。优点:
源码和资源文件打包在一起,整合度强,目录清晰。而且用pod package
和Pods
工程里生成的Framework都是会把资源文件打包进Framework里的。缺点:
需要修改原有[NSBundle mainBundle]
读取main bundle
里图片的方式为bundleForClass:
读取Framework里的资源的方式,需要修改一下源码。如果是静态Framework的话就没办法读取资源文件的。
pod package
是cocoapods的一个插件,如果没有的话使用以下命令安装:
sudo gem install cocoapods-packager
//强制覆盖之前已经生成过的二进制库
--force//生成静态.framework
--embedded//生成静态.a
--library//生成动态.framework
--dynamic//动态.framework是需要签名的,所以只有生成动态库的时候需要这个BundleId
--bundle-identifier//不包含依赖的符号表,生成动态库的时候不能包含这个命令,动态库一定需要包含依赖的符号表。
--exclude-deps//表示生成的库是debug还是release,默认是release。
--configuration=Debug
--configuration
--no-mangle
表示不使用name mangling技术,pod package
默认是使用这个技术的。我们能在用pod package
生成二进制库的时候会看到终端有输出Mangling symbols
和Building mangled framework
。表示使用了这个技术。
如果你的pod库没有其他依赖的话,那么不使用这个命令也不会报错。但是如果有其他依赖,不使用--no-mangle
这个命令的话,那么你在工程里使用生成的二进制库的时候就会报错:Undefined symbols for architecture x86_64
。
--subspecs
如果你的pod库有subspec,那么加上这个命名表示只给某个或几个subspec生成二进制库,--subspecs=subspec1,subspec2。生成的库的名字就是你podspec的名字,如果你想生成的库的名字跟subspec的名字一样,那么就需要修改podspec的名字。
这个脚本就是批量生成subspec的二进制库,每一个subspec的库名就是podspecName+subspecName
。
--spec-sources
一些依赖的source,如果你有依赖是来自于私有库的,那就需要加上那个私有库的source,默认是cocoapods
的Specs仓库。--spec-sources=private,https://github.com/CocoaPods/Specs.git
。
依赖类不重命名(编译通过):
pod package DSAudio.podspec --force --exclude-deps --no-mangle
--exclude-deps
表示不包含依赖库的符号,生成的静态库里就不会包含符号,我们静态库里使用了对应的依赖库的类的话,能看到为undefined
。比如我这里使用了AFNetworking
:
所以podspec
里要依赖对应的三方库:
Pod::Spec.new do |s|
.....
s.ios.vendored_framework = 'ios/DSAudio.framework'
s.dependency 'AFNetworking', '~> 2.3'
end
依赖类重命名(编译不通过):
pod package DSAudio.podspec --force --exclude-deps
不使用--no-mangle
,会把依赖类重命名为PodName_类名
,但这个符号依旧为undefined
,就算依赖的对应的库,静态链接的时候依然会找不到PodName_类名
,比如我这里使用了AFNetworking
,同时依赖了AFNetworking
,AFNetworking
编译生成的目标文件并没有PodDSAudio_AFHTTPRequestOperationManager
,就算静态链接了依然找不到对应的重命名过的类。
包含依赖类符号并重命名(编译通过):
pod package DSAudio.podspec --force
pod package
默认会把依赖库的类重命名为PodName_类名
,同时不使用--exclude-deps
会把依赖库的类都编译进来,这时我们能看到那个类不为undefined
了。这样的话如果主工程也依赖了这个三方库的话就不会出现符号冲突的问题了。
但如果三方库又依赖了系统库,对应的系统库是不会编译进来并且重命名的,这时如果没有依赖对应的系统库的话会出现符号找不到的报错:
所以要主动依赖一下那个三方库依赖的系统库:
Pod::Spec.new do |s|
.....
s.ios.deployment_target = '8.0'
s.ios.vendored_framework = 'ios/DSAudio.framework'
s.frameworks = 'MobileCoreServices', 'SystemConfiguration'
end
优点:这样就不会跟别人依赖的三方库出现版本不统一而出现问题的情况了
缺点:因为把依赖库都打进了静态库,会导致静态库的体积大了很多(以我这里为例,原来只有79kb,依赖库打进来之后有7.5M了)
创建好的Framework安装Version自动存放到不同目录脚本
pod package podName.podspec --force --embedded --no-mangle --exclude-deps --spec-sources=private,https://github.com/CocoaPods/Specs.git
pod package
打包不需要下载源码,只需要对应tag的.podspec
文件,它是根据.podspec
文件去对应tag直接下载源码进行打包的。
use_lib=1 pod install
在swift出来之后,因为swift是需要动态库的,原来用CocoaPods
集成的第三方库默认都是生成.a的静态库给主工程用的。所以CocoaPods
在Podfile里加了一个use_framework!
表示默认生成的是动态库。Build之后我们能在Pods工程下面的Products
目录看到生成的动态Framework。如果想要静态Framework,可以修改Pods工程下对应的target的Build Settings -> Mach-O Type
为Static Library
。
生成的FrameworkCocoaPods
会在主工程target下的Build Phases -> Embed Pods Frameworks
执行脚本嵌入到.app下的Framework文件夹下。
如果你在模拟器下Build那么生成的Framework支持的CPU架构就只有i386 和x86_64。如果是在Generic iOS Device
下Build的话那就只支持armv7和arm64。所以我们需要合成一个支持所有架构的Framework。
具体如何创建合成Framework可以参考iOS里的动态库和静态库。
可参考iOS App组件化开发实践里关于Debug/Release宏的部分。
工程里读取bundle里图片的方式都使用[NSBundle bundleForClass:]
而不是[NSBundle mainBundle]
因为[NSBundle bundleForClass:]
即可以读取动态Framework里的资源文件,又可以读取main bundle
里的资源文件。而[NSBundle mainBundle]
只能得到main bundle
里的资源文件。
打包静态Framework
资源文件不要放到Framework里面,单独打包成一个Bundle,从main bundle
里读取
因为Framework的umbrella保护伞的原因,在创建Pod的时候最好有一个跟Pod同名的.h头文件,里面是一些需要暴露的头文件。
图片资源最好自己打包成Bundle:s.resources = "#{s.name}/Assets/*.bundle"
,节省了cocoapods
帮你生成Bundle的时间。这里有一些xib,plist在Bundle里的时候的使用还没测试过。
embedded framework and dynamic framework