iOS CocoaPods组件平滑二进制化解决方案
iOS CocoaPods组件平滑二进制化方案及详细教程
感谢"fly2never_宝贝别哭"。可以使用cocoapods-packager这个插件来方便生成library(静态库,动态库都可以)。
强烈建议生成framework。
IS_SOURCE=1 pod package YTXChart.podspec --library --exclude-deps --spec-sources=http://gitlab.baidao.com/ios/ytx-pod-specs.git,https://github.com/CocoaPods/Specs.git 虽然有了这个方便的工具,但是了解一下打包的过程也是好的。
后记,有人问我为什么不改用Carthage。
可以看看我写的这一篇Carthage和iOS组件二进制化
CocoaPods和Carthage设计目的不一样。
我们的现在组织架构有多个iOS team,多个App。
- 已经使用了CocoaPods,如果要再集成Carthage比较麻烦。我们自己team还好说,还有其他team怎么办。
- 其他team已经用的是CocoaPods。对他们来说只需要升级版本号,要用源码时加上一个IS_SOURCE=1 pod install就可以了。够平滑。
- Carthage我要如何方便的切回源码调试。
- Carthage并不能解决版本更新要编译的问题。除非track那些framework,共享给Team中的其他人。那样的话你的git库就会很大。
说到底Carthage并不能解决实际应用的问提。
什么是组件二进制化?
在iOS开发中,事实标准是我们使用CocoaPods生成、管理和使用library。这里的library就是一个模块、组件或库。二进制化指的是通过编译把组件的源码转换成静态库或动态库,以提高该组件在App项目中的编译速度。
我们的方案是转换成静态库,也就是.a格式的文件加上暴露出来的头文件。
为什么我们需要二进制化呢?
在我们App开发中,我们逐渐的抽象了很多模块、业务、UI等把他转换成私有CocoaPod库。其中有一个是用C++和Objective-C混写的,源码格式为.mm。在app项目编译时.mm部分代码编译非常慢。这作为一个契机让我们去考虑如何加快编译速度。
这个混写的CocoaPod库叫做YTXChart,之后会以此库为例反复提到。
另外随着业务的扩展,私有CocoaPod库和第三方CocoaPod库越来越多,App项目中的文件也越来越多。每次pod install安装新库或pod update更新库的时候,重新编译的过程需要等待很长时间。这也向我们提出了加快编译速度的需求。
另外如果想要做组件化的话,一定要做二进制化。
所以我们想到了二进制化的方案来解决这个问题,并且很多大公司也是这么做的。
这带来一个新问题?一步就位还是平滑过度。
对我们来说,这是一个尝试,不可能开始就决定把所有的私有CocoaPod库二进制化,也不可能决定把所有第三方CocoaPod库二进制化。当务之急的情况是加快YTXChart库编译速度。所以必须找到一个方案平滑过度。
我们的App中的podflie是这样的
target 'jryMobile' do pod 'AFNetworking', '~> 2.6.3' pod 'Mantle', '~> 1.5.7' pod 'DateTools', '~> 1.7.0' pod 'ReactiveCocoa', '~> 2.3.1' pod 'CocoaAsyncSocket', '~> 7.4.1' pod 'FMDB', '~> 2.5' pod 'MWPhotoBrowser', '~> 1.4.1' pod 'MZFormSheetController', '~> 2.3.6' pod 'HMSegmentedControl', '~> 1.5.1' pod 'UMengAnalytics', '~> 3.5.8' pod 'UMengFeedback', '~> 2.3.4' pod 'TSMessagesNW', '~> 0.9.15' pod 'TPKeyboardAvoiding', '~> 1.2.9' pod 'SDWebImage', '~> 3.7' pod 'JHChainableAnimations', '~> 1.3.0' pod 'BarrageRenderer', '~> 1.7.0' pod 'MJRefresh', '~> 3.1.7' pod 'YTXAnimations', '~> 1.2.4', :subspecs => ["AnimateCSS", "Transformer"] pod 'YTXMediaIJKPlayer', '~> 0.2.1' pod 'YTXTradeBusinessType', '~> 1.1.0' pod 'YTXServerId', '~> 0.1.4' pod 'YTXUtilCategory','~> 1.2.0' pod 'YTXScreenShotManager', '~> 0.1.7' pod 'YTXRequest', '~> 1.0.0' pod 'YTXCommonSocket', '~> 0.1.9' pod 'YTXChartSocket', '~> 0.5.1'# 希望是二进制化的 pod 'YTXChart', '~> 0.17.0' pod 'YTXRestfulModel', '~> 1.2.2', :subspecs => ["RACSupport", "YTXRequestRemoteSync", "FMDBSync", "UserDefaultStorageSync"] pod 'YTXWebViewJavaScriptBridge', '~> 0.1.2' pod 'YTXCheckForAppUpdates', '~> 1.0.0' # pod 'YTXVideoAVPlayer', '~> 0.5.0' pod 'YTXChatUI', '~> 0.3.2' pod 'PNChart', '~>0.8.9' #pod 'EaseMobSDKFull', :git => 'https://github.com/easemob/sdk-ios-cocoapods-integration.git', :tag => '2.2.0' # EaseMobSDKFull 更新地址'https://github.com/easemob/sdk-ios-cocoapods-integration.git' #pod 'AFgzipRequestSerializer', '~> 0.0.2' pod 'AdhocSDK', '~> 2.2.1' pod 'FLEX', '~> 2.0', :configurations => ['Debug'] pod 'React', :path => './ReactComponent/node_modules/react-native', :subspecs => [ 'Core', 'RCTImage', 'RCTNetwork', 'RCTText', 'RCTWebSocket', # 添加其他你想在工程中使用的依赖。 ] pod 'CodePush', :path => './ReactComponent/node_modules/react-native-code-push'end 平滑二进制方案需求点
- 其他的CocoaPod库都还是源码。YTXChart为二进制化。
- 以后能够逐步迭代把更多的以YTX开头的CocoaPod库进行二进制化,而不影响主App。
- 能够提供一种方式把二进制化CocoaPod库切换回源码CocoaPod库以便调试。尽量做的方便。
- 解决YTXChart引用依赖的问题。(YTXChart还依赖了第三方AFNetworking和私有YTXServerId。保证生成的静态库中不会含有AFNetworking的内容和YTXServerId的内容并且能够编译通过)
- 利用原来的YTXChart.git,不创建新项目,不创建新的git库。因为我们的二进制化库的生成还是来自于源码,当源码更新时,我们需要一种非常快捷的方式去生成二进制的东西,不希望copy源码到某处,或者增加一个git submodule。
- 希望App源码和YTXChart中的源码尽量少或者没有改动。
- 希望App中的Podfile尽量少或者没有改动。
- 希望Podfile中的版本号保持风格一致,不会出现
'~> 2.2.1.binary'这种情况。 - 用原来的那一个CocoaPods Repo Spec。
以下这个解决方案的教程满足了以上所有需求点
注意,以下的例子基于Cocoapods@1.0.1,而且目前只能是1.0.1
第一步:源码生成静态库
如果你是通过命令pod lipo create创建的CocoaPod库并且pod install的话,它的目录结构应该像这样子(只列出重要的):
YTXChart |-Example |-YTXChart |-Pods |-YTXChart.xcodeproj |-YTXChart.xcworkspace |-Podfile \-Podfile.lock |-Pod |-Assets \-Classes \-YTXChart.podspec在xcode中创建新Target YTXChartBinaryFile->New->Target->Framework & Library->Cocoa Touch Static Library
如果你们的项目最低支持到iOS8可以创建Dynamic Framework
注意在Podfile中加入以下这段
target 'YTXChartBinary' doend 然后pod install
解释:Cocoapods@1.0.1会在Header Search Path自动加入内容。如果你用CocoaPods@0.39.0则需要自己加Header Search Path保证依赖库YTXServerId和AFNetwork能够被找到。如图:
然后把Pod/Classes中的源码拖入到YTXChartBinary中,这样选择(这样会link源码而不是复制):
然后变成这样子:
Headers需要自己加,里面是你需要暴露的头文件
在YTXChartBinary Target中的Build Settings下找到iOS Deployment Target选择和YTXChart.podspec中的s.platform保持一致。这里是7.0YTXChartBinary Target->Build Settings->iOS Deployment Target
在根目录创建shell脚本buildbinary.sh
你也可以创建一个Aggregate Target用来执行shell脚本
代码如下:
#获得当前目录的名字,一般是YTXChartSocket这种PROJECT_NAME=${PWD##*/}# 编译工程BINARY_NAME="${PROJECT_NAME}Binary"cd ExampleINSTALL_DIR=$PWD/../Pod/Products rm -fr "${INSTALL_DIR}" mkdir $INSTALL_DIR WRK_DIR=buildBUILD_PATH=${WRK_DIR}DEVICE_INCLUDE_DIR=${BUILD_PATH}/Release-iphoneos/usr/local/include DEVICE_DIR=${BUILD_PATH}/Release-iphoneos/lib${BINARY_NAME}.a SIMULATOR_DIR=${BUILD_PATH}/Release-iphonesimulator/lib${BINARY_NAME}.a RE_OS="Release-iphoneos" RE_SIMULATOR="Release-iphonesimulator"xcodebuild -configuration "Release" -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${BINARY_NAME}" -sdk iphoneos clean build CONFIGURATION_BUILD_DIR="${WRK_DIR}/${RE_OS}" LIBRARY_SEARCH_PATHS="./Pods/build/${RE_OS}" xcodebuild ARCHS=x86_64 ONLY_ACTIVE_ARCH=NO -configuration "Release" -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${BINARY_NAME}" -sdk iphonesimulator clean build CONFIGURATION_BUILD_DIR="${WRK_DIR}/${RE_SIMULATOR}" LIBRARY_SEARCH_PATHS="./Pods/build/${RE_SIMULATOR}"if [ -d "${INSTALL_DIR}" ] then rm -rf "${INSTALL_DIR}" fi mkdir -p "${INSTALL_DIR}"cp -rp "${DEVICE_INCLUDE_DIR}" "${INSTALL_DIR}/"INSTALL_LIB_DIR=${INSTALL_DIR}/lib mkdir -p "${INSTALL_LIB_DIR}"lipo -create "${DEVICE_DIR}" "${SIMULATOR_DIR}" -output "${INSTALL_LIB_DIR}/lib${PROJECT_NAME}.a" rm -r "${WRK_DIR}" 这个脚本写的并不是很好。说说主要做了什么。Release不同的静态库,真机和模拟器的。只构建x86_64,不构建i386加快速度
xcodebuild -configuration "Release" -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${BINARY_NAME}" -sdk iphoneos clean build CONFIGURATION_BUILD_DIR="${WRK_DIR}/${RE_OS}" LIBRARY_SEARCH_PATHS="./Pods/build/${RE_OS}" xcodebuild ARCHS=x86_64 ONLY_ACTIVE_ARCH=NO -configuration "Release" -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${BINARY_NAME}" -sdk iphonesimulator clean build CONFIGURATION_BUILD_DIR="${WRK_DIR}/${RE_SIMULATOR}" LIBRARY_SEARCH_PATHS="./Pods/build/${RE_SIMULATOR}" *通过lipo命令合并。新.a使用project name是因为要和App项目的OTHER_LDFLAGS兼容-l"YTXChart"
lipo -create "${DEVICE_DIR}" "${SIMULATOR_DIR}" -output "${INSTALL_LIB_DIR}/lib${PROJECT_NAME}.a"