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

3,034 阅读4分钟

一、背景

  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

最优解决方案,在podspec文件新增如下内容:

s.preserve_paths = "#{s.name}/Lib/**/*.framework","#{s.name}/Classes/**/*"

podspec 中配置 preserve_paths,确保缓存中同时存在源码和二进制的资源及文件,因为 pod 的缓存机制,如果不设置的话在源码和二进制切换时会产生文件的丢失,导致切换时会产生不可预知的问题。

方式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 https://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         = 'https://github.com/葛高召/ABC'
  # s.screenshots     = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { 'xxx' => 'xxx@gmailc.om' }
  s.source           = { :git => 'git@github.com:GE-GAO-ZHAO/ComommentSpecBinarySDK.git', :tag => s.version.to_s }

  # s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'  

  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文件实现,其实还是不太好,可以参考我的另一篇文章 juejin.cn/post/715949… 来避免这个问题