在制作 Pod私有库 之后,接下来我们在此基础上实现 Pod私有库的二进制化,什么是二进制化?其实通过CocosPods导入的我们制作的Pod私有库,实际是源码,会随着主工程一起编译,二进制化指的是通过编译把组件的源码转换成静态库或动态库,以提高该组件在App项目中的编译速度。打开 Pod 私有库示例工程,实现以下步骤:
1、添加 Static Library
在 xcode 中创建新 Target -> YXPlayerSDKBinary:
1YXPlayerSDK->New->Target->iOS->Framework & Library->Cocoa Touch Static Library如果项目最低支持到iOS8可以创建 Dynamic Framework。
将组件 YXPlayerSDK 的 Classes 文件夹下的文件 link 到 YXPlayerSDKBinary 中,如下:
1234567891011121314151617181920212223YXPlayerSDK├── Assets│ ├── YXPlayerSDKBinary.bundle│ │ ├── Info.plist│ │ └── playerPause@2x.png│ └── playerPause@2x.png├── Classes├── TestOne│ ├── TestViewController.h│ └── TestViewController.m└── TestTwo├── DoubboViewController.h└── DoubboViewController.m|||| YXPlayerSDK 中 Classes 文件里的文件拖到 YXPlayerSDKBinary 中(link源码而不是复制)||YXPlayerSDKBinary├── TestOne│ ├── TestViewController.h│ └── TestViewController.m└── TestTwo├── DoubboViewController.h└── DoubboViewController.m配置 YXPlayerSDKBinary
在 YXPlayerSDKBinary Target 中的 Build Phase 界面:
1、选择Editor\Add Build Phase\Add Copy Headers Build Phase
2、在 Compile Sources 和 Headers 中添加拖进的文件
设置 YXPlayerSDKBinary Target 中的 Build Settings 界面:
1、 iOS Deployment Target 选择和YXPlayerSDK.podspec 中的 s.platform 保持一致
2、Public Headers Folder Path 中配置为 include/YXPlayerSDKBinary
3、禁掉无效代码和debug用符号:
- Dead Code Stripping设置为NO
- Strip Debug Symbol During Copy 全部设置为NO
- Strip Style设置为Non-Global Symbols
完成上述配置,选择目标为iOS Device,按下command + B进行编译,一旦成功,工程导航栏中Product目录下 libYXPlayerSDKBinary.a 文件将从红色变为黑色,表明现在该文件已经存在了。右键单击 libYXPlayerSDKBinary.a,选择 Show in Finder。在此目录下,你将看到静态库 libRWUIControls.a 以及定为 public 的头文件在此也可看到。
2、将 Static Library 构建为 Framework
创建 framework 目录结构
12345678910YXPlayerSDKBinary.framework├── Headers -> Versions/Current/Headers├── Versions│ ├── A│ │ ├── Headers│ │ │ ├── DoubboViewController.h│ │ │ └── TestViewController.h│ │ └── YXPlayerSDKBinary│ └── Current -> A└── YXPlayerSDKBinary -> Versions/Current/YXPlayerSDKBinary在静态库构建过程中添加脚本来创建这种结构,在项目导航栏中选择 YXPlayerSDK,然后选择 YXPlayerSDKBinary 静态库目标,选择 Build Phases 栏,然后选择 Editor/Add Build Phase/Add Run Script Build Phase 来添加一个新的脚本在最后命名为 Build Framework:
1234567891011121314151617181920set -eexport FRAMEWORK_LOCN="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework"# Create the path to the real Headers diemkdir -p "${FRAMEWORK_LOCN}/Versions/A/Headers"# Create the required symlinks/bin/ln -sfh A "${FRAMEWORK_LOCN}/Versions/Current"/bin/ln -sfh Versions/Current/Headers "${FRAMEWORK_LOCN}/Headers"/bin/ln -sfh "Versions/Current/${PRODUCT_NAME}" \"${FRAMEWORK_LOCN}/${PRODUCT_NAME}"# Copy the public headers into the framework/bin/cp -a "${TARGET_BUILD_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}/" \"${FRAMEWORK_LOCN}/Versions/A/Headers"# Copy the public headers into the private pod/include/bin/cp -a "${TARGET_BUILD_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}/" \"${SRCROOT}/../${PROJECT_NAME}/Products/include"这个脚本首先创建了YXPlayerSDKBinary.framework/Versions/A/Headers目录,然后创建了一个framework所需要的三个连接符号(symbolic links)。
- Versions/Current => A
- Headers => Versions/Current/Headers
- YXPlayerSDKBinary => Versions/Current/YXPlayerSDKBinary
最后,将公共头文件从你之前定义的公共头文件路径拷贝到Versions/A/Headers目录下,-a参数确保修饰次数作为拷贝的一部分不会改变,防止不必要的重新编译。
多架构编译
每个CPU架构都需要不同的二进制数据,为了让创建的 framework 能在所有可能的架构上运行。就需要创建了二进制FAT(File Allocation Table,文件配置表),它包含了所有架构的片段(slice)。构建过程:
1、创建新的 Target 构建 framework:
1YXPlayerSDK->New->Target->Cross-platform->Other->Aggregate找到 Aggregate,点击Next,将目标命名为 Framework。为什么使用集合(Aggregate)目标来创建一个framework呢?为什么这么不直接?因为OS X对库的支持更好一些,事实上,Xcode直接为每一个OS X工程提供一个Cocoa Framework 编译目标。基于此,你将使用集合编译目标,作为Bash脚本的连接串来创建神奇的framework目录结构。
2、在库工程中选择Framework目标,在Build Phases中添加一个依赖。展开Target Dependencies面板,点击 + 按钮选择 YXPlayerSDKBinary 静态库。
3、在 Framework 静态库目标下,选择Build Phases栏,然后选择Editor/Add Build Phase/Add Run Script Build Phase来添加一个新的脚本在最后命名为 MultiPlatform Build:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798set -e# If we're already inside this script then dieif [ -n "$RW_MULTIPLATFORM_BUILD_IN_PROGRESS" ]; thenexit 0fiexport RW_MULTIPLATFORM_BUILD_IN_PROGRESS=1RW_FRAMEWORK_NAME="YXPlayerSDKBinary"RW_INPUT_STATIC_LIB="lib${RW_FRAMEWORK_NAME}.a"RW_FRAMEWORK_LOCATION="${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework"function build_static_library {# Will rebuild the static library as specified# build_static_library sdkxcrun xcodebuild -project "${PROJECT_FILE_PATH}" \-target "${TARGET_NAME}" \-configuration "${CONFIGURATION}" \-sdk "${1}" \ONLY_ACTIVE_ARCH=NO \BUILD_DIR="${BUILD_DIR}" \OBJROOT="${OBJROOT}" \BUILD_ROOT="${BUILD_ROOT}" \SYMROOT="${SYMROOT}" $ACTION}function make_fat_library {# Will smash 2 static libs together# make_fat_library in1 in2 outxcrun lipo -create "${1}" "${2}" -output "${3}"}# Extract the platform (iphoneos/iphonesimulator) from the SDK nameif [[ "$SDK_NAME" =~ ([A-Za-z]+) ]]; thenRW_SDK_PLATFORM=${BASH_REMATCH[1]}elseecho "Could not find platform name from SDK_NAME: $SDK_NAME"exit 1fi# Extract the version from the SDKif [[ "$SDK_NAME" =~ ([0-9]+.*$) ]]; thenRW_SDK_VERSION=${BASH_REMATCH[1]}elseecho "Could not find sdk version from SDK_NAME: $SDK_NAME"exit 1fi# Determine the other platformif [ "$RW_SDK_PLATFORM" == "iphoneos" ]; thenRW_OTHER_PLATFORM=iphonesimulatorelseRW_OTHER_PLATFORM=iphoneosfi# Find the build directoryif [[ "$BUILT_PRODUCTS_DIR" =~ (.*)$RW_SDK_PLATFORM$ ]]; thenRW_OTHER_BUILT_PRODUCTS_DIR="${BASH_REMATCH[1]}${RW_OTHER_PLATFORM}"elseecho "Could not find other platform build directory."exit 1fi# Build the other platform.build_static_library "${RW_OTHER_PLATFORM}${RW_SDK_VERSION}"# If we're currently building for iphonesimulator, then need to rebuild# to ensure that we get both i386 and x86_64if [ "$RW_SDK_PLATFORM" == "iphonesimulator" ]; thenbuild_static_library "${SDK_NAME}"fiecho "input_1 : ${BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}"echo "input_2 : ${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}"echo "output : ${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}"# Join the 2 static libs into 1 and push into the .frameworkmake_fat_library "${BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \"${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}"# Ensure that the framework is present in both platorm's build directoriescp -a "${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}" \"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework/Versions/A/${RW_FRAMEWORK_NAME}"# Copy the lib into the private pod/libditto "${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework/Versions/A/${RW_FRAMEWORK_NAME}" \"${SRCROOT}/../${PROJECT_NAME}/Products/lib/${RW_INPUT_STATIC_LIB}"# Copy the framework to the pod/frameworkditto "${RW_FRAMEWORK_LOCATION}" \"${SRCROOT}/../${PROJECT_NAME}/Products/Framework/${RW_FRAMEWORK_NAME}.framework"# Copy the resources bundle to pod/Assetsditto "${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.bundle" \"${SRCROOT}/../${PROJECT_NAME}/Assets/${RW_FRAMEWORK_NAME}.bundle"从脚本上最后可以看出,我们把构建出来的二进制文件放在 YXPlayerSDK 组件文件下的Products 文件中,所以在构建之前,我们先在 YXPlayerSDK 组件文件下增添一个 Products 文件存放 .a 和 .frameworky 以及它们的头文件,目录结构如下:
1234567YXPlayerSDK├── Assets├── Classes└── Products├── Framework #存放 Framework 文件├── include #存放相关头文件└── lib #存放 .a 文件选择Framework集合方案(aggregate scheme),按下cmd+B编译该framework。构建完成之后,就能看到
Products 文件夹下添加完之后的目录结构:
1234567891011121314151617Products├── Framework│ └── YXPlayerSDKBinary.framework│ ├── Headers -> Versions/Current/Headers│ ├── Versions│ │ ├── A│ │ │ ├── Headers│ │ │ │ ├── DoubboViewController.h│ │ │ │ └── TestViewController.h│ │ │ └── YXPlayerSDKBinary│ │ └── Current -> A│ └── YXPlayerSDKBinary -> Versions/Current/YXPlayerSDKBinary├── include│ ├── DoubboViewController.h│ └── TestViewController.h└── lib└── libYXPlayerSDKBinary.a
3、打包 Bundle 资源
打开 YXPlayerSDK工程,点击Add Target按钮添加新的 target,导航到OS X/Framework and Library/Bundle。将新的Bundle命名为YXPlayerSDKResources,这里需要配置几个编译设置:
因为正在创建一个在iOS上使用的bundle,这与默认的OS X不同。选择YXPlayerSDKResources目标,然后点击Build Settings栏,搜索base sdk,选择Base SDK这一行,按下delete键,这一步将OS X切换为iOS。
同时你需要将工程名称改为YXPlayerSDKResources。搜索product name,双击进入编辑模式,将${TARGET_NAME}替换为YXPlayerSDKResources。
默认情况下,有两种resolutions的图片可以产生一些有趣的现象。例如,当导入一个retina @2x版本的图片时,普通版的和Retina版的将会合并成一个多resolution的TIFF(标签图像文件格式,Tagged Image File Format)。这不是一件好事。搜索hidpi将COMBINE_HIDPI_IMAGES设置为NO。
确保编译framework时,bundle也能被编译并将framework作为依赖添加到集体目标中。选中Framework目标,选择Build Phases栏,展开Target Dependencies面板,点击 + 按钮,选择RWUIControlsResources目标将其添加为依赖。
在Framework目标的Build Phases中,打开MultiPlatform Build面板,在脚本的最后添加下述代码,这条指令将拷贝构建好的bundle指定位置:
123# Copy the resources bundle to pod/Assetsditto "${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.bundle" \"${SRCROOT}/../${PROJECT_NAME}/Assets/${RW_FRAMEWORK_NAME}.bundle"
4、解决二进制化的依赖
使用源码的话,依赖第三方库或私有库,在 podspec 文件添加 s.dependency 就行,编译的时候 CocoaPods 会自己做好依赖的 link,但二进制化,是我们自己先编译好 .a 或 .framework,这种依赖怎么做呢?
1、在 YXPlayerSDK 组件工程里的 Podfile 添加如下:
|
|
2、在 podspec 文件添加如下,二进制化如果使用的是 .a 就是设置 vendored_libraries ,是 .framework 就设置 vendored_frameworks,如下:
|
|
注意一下,Podfile 里面依赖库的要和 podspec 依赖的库保持一致。
3、为了方便调试,加入环境变量 IS_SOURCE 实现Pod私有库源码和二进制的其换,完整的 YXPlayerSDK.podspec 如下:
|
|
使用源码编译命令 :IS_SOURCE=1 pod install
使用二进制编译命令:pod install
4、配置完依赖库后,执行 pod install 加载依赖库,然后按下cmd+B 重新编译该framework,可能会遇到以下报错:
- 1pod error: The 'xxxx ' target has transitive dependencies that include static binaries ...
这是因为 Podfile 中加上 use_frameworks!,开启这个选项之后,所有以源码引入的pod都会编译成动态链接库,而如果依赖的Pod里面的库包含静态库,这样就造成 pod install 时会把静态库编译到App里面,源码编译成的动态库没法依赖它,出现上面的错误。
解决方法:Pod私有库不以源码导入,而是二进制化后导入,再 pod install。
- 12build error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/libtool: can't locate file for: -lPods-YXPlayerSDKBinarybuild error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/libtool: file: -lPods-YXPlayerSDKBinary is not an object file (not allowed in a library)
这是因为执行 pod install ,整个项目按照先编译被依赖Pod,然后依赖其他Pod的Pod也被构建出来,最终所有的组件被编译为一个lib-Pods-XXX.a 被添加进项目进去。
解决方法:去掉 Build Phases 的 Link Binary With Libraries 中的 libPods-YXPlayerSDKBinary.a,然后重新编译。
5、调试二进制化的Pod私有库
接下来就可以在组件示例工程中引入调试,先本地引入调试,在 YXPlayerSDK 的 Podfile 中添加如下:
|
|
运行成功之后,就可以推送到git仓库,打上tag,然后验证Pod私有库,推送到远程私有Spec Repo,再引入调试:
|
|
完成之后,脱离组件示例工程再新建一个项目Demo,pod 引入私有库,注意几个问题:
- 私有库二进制化后,Podfile 中需要开启 use_frameworks!,不然报错如下:
|
|
- Pod私有库,二进制化使用的是 framework, 如果编译项 OTHER_LDFLAGS 中为 -l”YXPlayerSDKBinary” ,需要改为-framework “YXPlayerSDKBinary”,反之亦然,不然报错如下:
|
|
示范例代码:Private-Pod-Example