iOS CocoaPods组件平滑二进制化解决方案

JerryXia 发表于 , 阅读 (0)

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能够被找到。如图:headersearchpath

然后把Pod/Classes中的源码拖入到YTXChartBinary中,这样选择(这样会link源码而不是复制):default然后变成这样子:defaultHeaders需要自己加,里面是你需要暴露的头文件

在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"