iOS 组件二进制化方案--(一)

背景

随着业务的扩展,私有CocoaPod库和第三方 CocoaPod 库越来越多,App项目中的文件也越来越多。每次 pod install/update 或提交到 Jenkins 上构建的时候,重新编译的过程需要等待很长时间,这就间接地向我们提出了加快编译速度的需求。

什么是组件二进制化?

开发过程中我们使用CocoaPods生成、管理和使用模块库、组件库或业务库,在工程中组件库或业务库均可以看成一个模块。二进制化指的是通过编译把模块的源码转换成静态库或动态库,以提高该组件在App项目中的编译速度。

期望效果

1、提交至 Jenkins 的构建,采用二进制;
2、二进制文件与源码仓库权限一致;
3、podfile导入时,可在源码和二进制文件之间切换;

cocoapods-packager 生成二进制文件

cocoapods-packager是 cocoapods 的一个自动化打包插件,可以用来打包生成动态库或静态库,如果没有的话使用以下命令安装:

sudo gem install cocoapods-packager

部分参数配置:

//强制覆盖之前已经生成过的二进制库
--force

//生成静态.framework
--embedded

//生成静态.a
--library

//生成动态.framework
--dynamic

//动态.framework是需要签名的,所以只有生成动态库的时候需要这个BundleId
--bundle-identifier

//不包含依赖的符号表,生成动态库的时候不能包含这个命令,动态库一定需要包含依赖的符号表。
--exclude-deps

//表示生成的库是debug还是release,默认是
release。--configuration=Debug
--configuration

//表示不使用name mangling技术,pod package默认是使用这个技术的。如果你的pod库没有其他依赖的话,那么不使用这个命令也不会报错。但是如果有其他依赖,不使用--no-mangle这个命令的话,那么你在工程里使用生成的二进制库的时候就会报错:Undefined symbols for architecture x86_64。
--no-mangle

创建静态Framework

pod package SAKit.podspec --force --embedded --spec-sources=ssh://git@192.168.6.115:7999/xdwios/saspecs.git,https://github.com/CocoaPods/Specs.git

创建静态 .a 便是用 --library 替换 --embedded;

pod package SAKit.podspec --force --library --spec-sources=ssh://git@192.168.6.115:7999/xdwios/saspecs.git,https://github.com/CocoaPods/Specs.git

步骤

  1. 代码库打tag;
  2. 执行 pod package 命令,并将生成的 framework 按指定目录存放;
  3. 提交代码并重新打tag(与第1步tag相同),先移除再添加;
  4. 提交版本至 repo 库;

通过 pod-packager 生成的二进制文件支持的指令集:armv7 armv7s i386 x86_64 arm64

xcodebuild 生成二进制文件

xcodebuild 命令是 Xcode Command Line Tools 的一部分。通过调用这个命令,可以完成 iOS 工程的编译,打包和签名过程。
我们这里用到的是编译;

命令行执行 man xcodebuild 或 xcodebuild -h 可查看参数配置,常用的如下:

xcodebuild.png

以工程名字为 SATestProject 为例,步骤:
1、编译

xcodebuild -workspace Example/SATestProject.xcworkspace -scheme SATestProject -configuration Release -verbose -derivedDataPath Library/DerivedDataPath

2、更换目录

mv Library/DerivedDataPath/Build/Products/Release-iphoneos/SATestProject/libSATestProject.a Library/SATestProject.a

rm -rf Library/DerivedDataPath

3、打tag并提交至repo库;

当前 xcode 版本 9.0,通过 xcodebuild 编译默认生成的二进制文件支持的指令集:armv7 arm64
通过 build-setting中设置 Architectures ,添加 armv7s 后,再编译生成的 .a 支持的指令集:armv7 arm64 armv7s:

set_archectures.png

经测试发现:cocoapods-packager打包生成的.a会将已用到的其他库的文件打进去;而采用xcodebuild编译打包则不会;

编辑podfile文件,配置.a文件支持的指令集

以下代码为通过podfile配置所生成的.a支持的指令集,if判断是因为Cocoapods升级到0.38或0.39版本后installer_representation.project.targets.each中的project改名了,变成了pods_project,为适配不同版本的cocoapods的设置

post_install do |installer_representation|

        if defined? installer_representation.project
                installer_representation.project.targets.each do |target|
                        target.build_configurations.each do |config|
                                config.build_settings['ARCHS'] = 'armv7s arm64'
                                config.build_settings['VALID_ARCHS'] = 'armv7s arm64'
                        end
                end
        end

    
        if defined? installer_representation.pods_project
                installer_representation.pods_project.targets.each do |target|
                        target.build_configurations.each do |config|
                                config.build_settings['ARCHS'] = 'armv7s arm64'
                                config.build_settings['VALID_ARCHS'] = 'armv7s arm64'
                        end
        end
        end
end

附:Xcode Build Settings Reference

源码和二进制之间的切换

podspec 文件

为了编译快我们可能会使用二进制,但是有时候我们需要切换回源码进行调试代码。我们在pod仓库中同时存放源码和生成的二进制库,然后就是让CocoaPods在安装Pod的时候判断使用源码还是二进制库。以 SAKit 为例,导入文件路径及依赖部分的写法如下:

#控制安装 Pod 的时候判断使用源码还是二进制库
$lib = ENV['use_lib']
$lib_name = ENV["#{s.name}_use_lib"]

if $lib || $lib_name
    s.ios.vendored_libraries = "Library/#{s.version}/#{s.name}.a"
    s.source_files = "#{s.name}/Classes/**/*.h"
    #这种是你已经打包好了bundle,推荐这种,可以省去每次pod帮你生成bundle的时间
    s.resources = "#{s.name}/Resource/*.bundle", "#{s.name}/Resource/SAKit_Temp.xcassets/*.png"
else
    #源码
    s.source_files = "#{s.name}/Classes/**/*.{h,m}"
    s.resources = "#{s.name}/Resource/*.bundle", "#{s.name}/Resource/SAKit_Temp.xcassets/*.png"
    s.dependency 'Masonry'
    s.dependency 'MJRefresh'
    s.dependency 'SALocalizable'
    s.dependency 'Aspects'
    s.dependency 'WebViewJavascriptBridge'
    s.dependency 'SAImageCropper'
    s.dependency 'SDWebImage'
    s.dependency 'SAFoundation'
end


#第一次安装时缓存两种格式的文件,解决 切换为另一种格式的时候发现Pod目录下为空,没有需要的文件 的问题
s.preserve_paths = "#{s.name}/Classes/**/*","#{s.name}/Assets/*.{png,xib,plist}","Library/#{s.version}/#{s.name}.a"

具体使用可参照以上写法根据实际情况对路径、resource等稍作调整;

源码或二进制导入

使用源码:

直接运行 pod install

所有库拉取静态库Framework

use_lib=1 pod install

指定 SAKit 使用二进制导入

SAKit_use_lib=1 pod install

.a文件提交控制

因重新生成.a文件时,git会将整个.a重新保存,会导致git仓库容量被大量占用,因此在生成某一版本.a文件时,因此在之前的所有提交记录包含此版本.a文件的提交记录中将其删除;

通过git-filter-branch实现;

git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch path-to-your-remove-file' --prune-empty --tag-name-filter cat -- --all

初步方案

  1. 修改podspec;
  2. 若之前生成过此版本,在包含的提交记录中将其删除;
  3. xcodebuild 编译生成 .a,存放至指定目录;
  4. 打tag,提交至repo;

2、3两步可通过执行 salib 脚本即可完成,生成的 .a 自动存放在工程根目录的 Library 文件夹内;

  • 编译 podspec 文件同名的 scheme,需在工程根目录下执行;

salib

  • 指定 scheme 编译,需在工程根目录下执行

salib PINCache

  • 指定 xcworkspace 文件及需要编译的 scheme,在任何目录下执行都可以

salib Desktop/SAFoundation/Example/SAFileUpload.xcworkspace SAUpload

下面贴的是脚本内容:

#!/bin/bash
#
#------------iOS 组件二进制化实现方法--------------

#------------获取 workspace 与 scheme 路径---------------
if [ $# == 0 ]; then    #不存在参数
     
    if [ -e *.podspec ];then  
        
        #存在 podspec 文件
        #采用 podspec 的名字指定 workspace 与需要编译的 scheme
        project_name=$(basename *.podspec .podspec)  
        workspace_name=Example/$project_name.xcworkspace
        
    else
        echo "ERROR:not find podspec file, please run shell in project root path"
    exit 1
    fi
    
else
    
    if [ -n "$2" ]; then
        #手动指定 workspace 和 scheme
        workspace_name=$1 # workspace name
        project_name=$2 # scheme name
        project_root_path=$(cd `dirname $workspace_name`; cd ../; pwd;)
    else
        #手动指定 scheme
        project_name=$1 # scheme name
        if [ -e Example/*.xcworkspace ];then  #存在 xcworkspace 文件
            workspace_name=Example/*.xcworkspace
        else
            echo "ERROR: not find xcworkspace file, please run shell in project root path"
            exit 1
        fi
    fi
fi

#------------设定生成二进制对应的版本号,配置.a文件存储路径---------------
echo "--- Create library for $project_name ---"
echo
echo "please input the version which you want to build:"

read version

#------------配置存储路径---------------
if [ -n "$project_root_path" ]; then
    base_path=$project_root_path/Library
else
    base_path=Library
fi

if [ -n "$version" ]; then
    mkdir $base_path/$version
    version_base_path=$base_path/$version
else
    mkdir $base_path/0.1.0
    version_base_path=$base_path/0.1.0
fi


# ------------删除之前生成.a的提交记录---------------------------------
if [ -e $version_base_path/lib$project_name.a ];then

    cd $base_path && cd ../
    echo "删除记录"
    
    git filter-branch --force  --index-filter "git rm -r --cached --ignore-unmatch $version_base_path/lib$project_name.a" HEAD

    if [ $?  -eq 0 ]; then
        git push --all --force
    else
        echo
        echo "--- BUILD FAILED ---"
        exit 1
    fi

fi

#--------------清空Library———————————————
rm -rf $base_path/*
    
# ------------生成.a文件,并存储在对应路径下---------------------------------
xcodebuild -workspace $workspace_name -scheme $project_name -configuration Release -verbose -derivedDataPath $version_base_path/DerivedDataPath

if [ $?  -eq 0 ]; then
    mv $version_base_path/DerivedDataPath/Build/Products/Release-iphoneos/$project_name/lib$project_name.a  $version_base_path/lib$project_name.a
    rm -rf $version_base_path/DerivedDataPath

    lipo -info $version_base_path/lib$project_name.a
else
    echo
    echo "--- BUILD FAILED ---"
    exit 1
fi

exit 0

不足

  • 二进制文件存放于源码库,污染源码库;
  • 每个业务都得去修改podspec文件,以配置区别拉取二进制文件和源码;



©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容