iOS组件二进制方案之prepare_command实现

语言: CN / TW / HK

一、背景

  1. 目前不方便在主工程里使用源码联调
  2. 目前每一个基础库依赖外部的工具静态库非常困难或者是引进进来工程角色越来越不清晰,架构规划未来会越来越不好划分

二、入门要求

  1. cocoapods组成以及核心模块工作原理
  2. pod update 和 pod install 原理
  3. 简单了解ruby语法

三、期望目标

  1. Pod update时对已经push到spec仓库的pod库可以通过tag或者全局ruby环境变量对区分是加载framework或者是源码;

四、行业方案汇总

方式1:

pod repo push 时,我们可以发布两套podspec,eg: ABC.podspec 和 ABC_Binary.podspec 成为双podspec方案;

方式2:

CocoaPods提供了针对podspec的预执行脚本,prepare_command(戳我进官网)命令,该命令可以指定相应的脚本在pod install时去执行,那么我们就可以将编译打包的脚本放入spec文件中,从而完成延迟打包

A bash script that will be executed after the Pod is downloaded. This command can be used to create, delete and modify any file downloaded and will be ran before any paths for other file attributes of the specification are collected.

此命令可用于创建,删除和修改下载的任何文件,并且在收集规范的其他文件属性的任何路径之前将ran,😄😄😄 似乎发现了新大陆。

注意:如果是:path方式引入这个脚本不起作用 (官方有具体的描述,我的理解是本地这样操作是冗余的)

扩展prepare_command 使用方式

格式:

``` s.prepare_command = '脚本类型 脚本路径'

s.prepare_command = <<-CMD ... 指令 CMD ```

例子如下:

``` s.prepare_command = 'ruby build_files.rb'

s.prepare_command = '/bin/bash build_files.sh'

s.prepare_command = '/bin/bash build_files.sh'

s.prepare_command = <<-CMD

                    linux unix各种执行指令,eg:操作文件

CMD ```

这种方式运行之前需要执行以下指令删除pod库在本地缓存以及工程缓存,我这里比较强暴点😄,具体如下:

``` pod cache clean --all

rm -rf ~/Library/Caches/CocoaPods ```

方式3:

通过cocoapods插件 cocoapods-binary 实现

第一步:sudo gem install cocoapods-binary

第二步:例如如下podfile的写法:

``` plugin 'cocoapods-binary'

use_frameworks!

all_binary!

target "HP" do

pod "ExpectoPatronum", :binary => true

end ```

方案4:

目前还有一种方式,仅限于利用xcode打包时保留的DWARF调试信息临时还原出源码,具体可以参考iOS组件二进制源码调试热切换方案,但是跟我们这次目标不符合,感兴趣也可以了解😄;

调研方案对比

| 方案 | 优点 | 缺点 | | -------------------- | -------------------------------------------------------- | ------------------------------------------ | | 双podspec方案 | 加速pod 导入的速度 | 需要占用部分服务资源 | | prepare_command 延迟打包 | 发布简单,只需根据ruby环境变量确定是否需要调用prepare_command的frmaework打包脚本即可 | 因为这种方式是拉取代码,延迟决定是否打framework形式导入,相比第一种时间会长 | | cocoapods-binary | 外部使用方做的时候过多 | |

五、demo实践

实现步骤:

方案:demo使用的prepare_command 延迟打包方式打包,这里采用的.a静态库的形式打包

源码仓库 dev分支

Spec仓库

第一步

熟悉ABC.podspec文件,如何分别区分源码和二进制库,以及二进化的话在什么时机和如何打包静态库 ```

Be sure to run `pod lib lint ABC.podspec' to ensure this is a

valid spec before submitting.

Any lines starting with a # are optional, but their use is encouraged

To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html

Pod::Spec.new do |s|   s.name             = 'ABC'   s.version          = '0.1.0'   s.summary          = 'A short description of ABC.'

This description is used to generate tags and improve search results.

* Think: What does it do? Why did you write it? What is the focus?

* Try to keep it short, snappy and to the point.

* Write the description between the DESC delimiters below.

* Finally, don't worry about the indent, CocoaPods strips it!

s.description      = <<-DESC TODO: Add long description of the pod here.   DESC

s.homepage         = 'http://github.com/葛高召/ABC'   # s.screenshots     = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'   s.license          = { :type => 'MIT', :file => 'LICENSE' }   s.author           = { 'xxx' => '[email protected]' }   s.source           = { :git => '[email protected]:GE-GAO-ZHAO/ComommentSpecBinarySDK.git', :tag => s.version.to_s }

# s.social_media_url = 'http://twitter.com/'

s.ios.deployment_target = '9.0'

if s.version.to_s.include?'Binary' or ENV['IS_BINARY']      puts '-------------------------------------------------------------------'      puts 'Notice:ABC is binary now'      puts '-------------------------------------------------------------------'      s.prepare_command = '/bin/bash build_lib.sh'      s.ios.vendored_frameworks= 'PodProducts/.framework'    else      puts '-------------------------------------------------------------------'      puts 'Notice:ABC is source code now'      puts '-------------------------------------------------------------------'      s.static_framework = false      s.source_files = 'ABC/Classes//'    end end ```

第二步

具体静态库打包脚本如下(仅做参考) ```

!/bin/sh

Script.sh

ABC

Created by 葛高召 on 2022/1/6.

Copyright © 2022 葛高召. All rights reserved.

echo ///                        /// echo /// 🚀开始延迟编译二进制库🚀  /// echo ///                       ///

read_dir() {     for file in ls $1 do if [ -d $1"/"$file ]     then read_dir $1"/"$file     else echo $1"/"$file     fi     done }

echo ======😂😂😂目录信息😂😂😂========= CURRENT_DIR1=$(cd dirname $0; pwd) read_dir $CURRENT_DIR1 echo ======😂😂😂目录信息😂😂😂=========

workspace名、scheme名字

PROJECT_NAME='ABC' BINARY_NAME="${PROJECT_NAME}"

进入工程根目录

cd Example

framework路径

INSTALL_DIR=../PodProducts if [ -d ${INSTALL_DIR} ];then     echo "移除framework缓存  $INSTALL_DIR"     rm -rf ${INSTALL_DIR} else     echo "创建framework路径  $INSTALL_DIR"     mkdir -p $INSTALL_DIR fi

编译场地

BUILD_PATH="${CURRENT_DIR1}/build" RE_OS="Release-iphoneos" RE_SIMULATOR="Release-iphonesimulator" DEVICE_DIR_FOLDER="${BUILD_PATH}/${RE_OS}" SIMULATOR_DIR_FOLDER="${BUILD_PATH}/${RE_SIMULATOR}" DEVICE_DIR="${DEVICE_DIR_FOLDER}/${BINARY_NAME}.framework" SIMULATOR_DIR="${SIMULATOR_DIR_FOLDER}/${BINARY_NAME}.framework"

echo ======😂😂😂编译场地信息😂😂😂========= CURRENT_DIR2=$(cd dirname $0; pwd) echo "CURRENT_DIR: ${CURRENT_DIR2}" echo "BUILD_PATH: ${BUILD_PATH}" echo "DEVICE_DIR_FOLDER: ${DEVICE_DIR_FOLDER}" echo "SIMULATOR_DIR_FOLDER: ${SIMULATOR_DIR_FOLDER}" echo "DEVICE_DIR: ${DEVICE_DIR}" echo "SIMULATOR_DIR: ${SIMULATOR_DIR}" echo ======😂😂😂编译场地信息😂😂😂========

分别编译模拟器和真机的Framework

xcodebuild -configuration "Release" -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${BINARY_NAME}" ONLY_ACTIVE_ARCH=NO MACH_O_TYPE="staticlib" -sdk iphoneos CONFIGURATION_BUILD_DIR="${DEVICE_DIR_FOLDER}" clean build xcodebuild -configuration "Release" -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${BINARY_NAME}" ONLY_ACTIVE_ARCH=NO MACH_O_TYPE="staticlib" ARCHS='i386 x86_64' VALID_ARCHS='i386 x86_64' -sdk iphonesimulator CONFIGURATION_BUILD_DIR="${SIMULATOR_DIR_FOLDER}" clean build

echo ======😂😂😂目录信息😂😂😂========= read_dir ../ echo ======😂😂😂目录信息😂😂😂=========

if [ -d "${DEVICE_DIR}/" ];then     echo "exist ${DEVICE_DIR}" else     echo "termination | reason: not exist ${DEVICE_DIR}"     exit fi

if [ -d "${SIMULATOR_DIR}/" ];then     echo "exist ${SIMULATOR_DIR}" else     echo "termination | reason: not exist ${SIMULATOR_DIR}"     exit fi

合成fat库

INSTALL_LIB_DIR=${INSTALL_DIR}/${BINARY_NAME}.framework if [ -d "${INSTALL_LIB_DIR}" ] then rm -rf "${INSTALL_LIB_DIR}" fi

mkdir -p "${INSTALL_LIB_DIR}" cp -a "${DEVICE_DIR}/" "${INSTALL_LIB_DIR}/" lipo -create "${DEVICE_DIR}/${BINARY_NAME}" "${SIMULATOR_DIR}/${BINARY_NAME}" -output "${INSTALL_LIB_DIR}/${BINARY_NAME}"

删除编译产物

rm -rf $BUILD_PATH

echo ///                        /// echo /// 🚀完成延迟编译二进制库🚀  /// echo ///                       ///

```

第三步

整体入口脚本,在podfile同级目录放入下面的脚本直接运行即可

```

!/bin/sh

Script.sh

ABC

Created by 葛高召 on 2022/1/6.

Copyright © 2022 葛高召. All rights reserved.

提示导入的pod库形式

read -p "请输入安装的形式? 是:1(二进制) , 否:0(源码) " binary

清除pod缓存

pod cache clean --all rm -rf ~/Library/Caches/CocoaPods

移除Podfile.lock 和 Pods/ABC文件夹

work_path=$(pwd) echo 当前目录:$work_path PodfileLockDir=${work_path}/Podfile.lock PodsDir=${work_path}/Pods

if [[ ! -f "$PodfileLockDir" ]]; then echo "PodfileLockDir文件夹不存在" else rm -f $PodfileLockDir fi

if [[ ! -d "$PodsDir" ]]; then echo "PodsDir文件夹不存在" else rm -rf $PodsDir fi

安装

case $binary in 0) echo "安装源码库" pod update --verbose ;; 1) echo "安装二进制库" IS_BINARY=1 pod update --verbose ;; *) echo '输入错误,异常退出' ;; esac

``` 运行根据提示输入不同的配置,实现导入二进制库或者源码库

六、参考文献

Podspec****

pod install vs. pod update

script_phases****

How to use CocoaPods plugins****

cocoapods-imy-bin无侵入式

cocopods源码分析

七、后语

这里介绍iOS组件二进化常见的方式,以及使用ruby环境变量和prepare_command在pod install之前通过脚本提交打好静态库方式实现。缺陷是发布后开发者可以看到podspec文件实现,其实还是不太好,可以参考我的另一篇文章 http://juejin.cn/post/7159494385204199437 来避免这个问题