Swift热更新-适合多人开发

6,085 阅读4分钟

Swift接入例子-适合多人协作

文档已过时,欢迎去官网看新的文档。

「 Swift接入例子 」中介绍了Swift项目如何接入SOT。但是要求SDK解压到特定目录中,编译配置的路径也是绝对路径,不适合多人协作开发。文本介绍适合多人开发协作的接入方法。

还是以开源的「 SwiftMessages 」Demo为例,该工程全部用Swift语言开发。这里把修改好的版本上传到了git上,分支为 「 sotcollaboration 」SotDebug接入免费版,SotRelease接入网站版,读者只需要进行下面的 Step1.配置编译环境 就可以直接用该分支测试。

现在开始从头讲解,git clone原本的工程后(我的路径为/Applications/SwiftMessages),命令行cd /Applications/SwiftMessages进入根目录,使用版本切换命令:git checkout 1e49de7b3780b699(因本文档制作于21年10月21号,以当日版本为准)。首先进入Demo目录,打开Demo.xcodeproj工程,scheme默认就已经选中了Demo:...

我使用的是Xcode12.4,可以直接编译成功,启动APP能看到画面(模拟器):

......

点击最上面的MESSAGE VIEW控件,会弹出一个错误提示窗口,今天我们就来用SOT热更的方式修改错误提示的文案:...

Step1: 配置编译环境

「 下载SOT的SDK 」,解压到项目目录下 /Applications/SwiftMessages/Demo/sotsdk...

在terminal运行命令:sh /Applications/SwiftMessages/Demo/sotsdk/compile-script/install.sh安装SOT编译工具链,需要输入密码。 如果有报错,则可能是xcrun命令错误,需要先运行一下xcode-select --install安装xcode命令行工具,然后运行 sudo xcode-select --switch Xcode.app的路径,把”xcode.app的路径“改成你mac上的xcode的路径,例如我的是/Applications/Xcode.app。再运行install.sh脚本即可。

可以自己检查一下是否安装成功,命令行运行:xcrun --find sot_link.sh,例如我的会得到结果 /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/sot_link.sh,进入到该目录还能看到其余安装进去的脚本:...

用文本编辑器打开 /Applications/SwiftMessages/Demo/sotsdk/project-script/sotconfig.sh,修改EnableSot=1:...新版SDK已经不会再使用sotconfig.sh里的sdkdir,sotbuilder和objbuilder路径了,所以不用修改这些配置了,删掉也可以。

Step2: 增加Configuration

增加两个Configuration,只有切换到这两个Configuration才使用SOT编译模式,平时还是用原来的Configuration做开发,步骤如下:

  1. 选中Demo Project,然后选择Info面板,点击Configurations的下面加号,复制Debug的编译配置,并且命名为SotDebug,用来接入免费版的SOT。再选择复制Release编译配置,命名为SotRelease,用来配置网站版的SOT,注意名字都不要留有空格:...加完就是:...
  2. SwiftMessages也加上这两个Configuration:...

注意:读者应用到自己项目中时,需要把所有的工程都加上这两个Configuration,否则编译会报找不到文件等等的错误。所以加完这两个Configuration之后,就马上切换到它们去Build和Run一下,看是否有编译错误,如果没有再进行下面的操作,如果有,请检查是否漏了一些工程没有添加上。

Step3: 修改编译选项

添加热更需要的编译选项,添加SOT虚拟机静态库等,步骤如下:

  1. 选中Demo工程,然后选择Demo这个Target,再选择Build Settings:...

  2. Other Linker FlagsSotDebug中添加-sotmodule $(PRODUCT_NAME) sotsdk/libs/libsot_free.a -sotsaved $(SRCROOT)/sotsaved/$(CONFIGURATION)/$(CURRENT_ARCH) -sotconfig $(SRCROOT)/sotsdk/project-script/sotconfig.sh

    SotRelease中添加-sotmodule $(PRODUCT_NAME) sotsdk/libs/libsot_web.a -sotsaved $(SRCROOT)/sotsaved/$(CONFIGURATION)/$(CURRENT_ARCH) -sotconfig $(SRCROOT)/sotsdk/project-script/sotconfig.sh

    每个选项的意义如下:

    • -sotmodule是module的名字,可以直接用$(PRODUCT_NAME),也可以自定义名字,名字不要有空格
    • -sotsaved是编译中间产物保存的目录,补丁自动化生成需要对比前后编译的产物来生成补丁
    • -sotconfig指定了项目sotconfig.sh的路径,该脚本控制sot编译器的工作
    • sotsdk/libs/libsot_free.a是SOT虚拟机静态库的路径,链接的是免费版的虚拟机
    • sotsdk/libs/libsot_web.a是SOT虚拟机静态库的路径,链接的是网站版的虚拟机
  3. Other C Flags以及Other Swift Flags的SotDebug和SotRelease下添加-sotmodule $(PRODUCT_NAME) -sotconfig $(SRCROOT)/sotsdk/project-script/sotconfig.sh,意义跟上一步是一样的,需要保持一致。经过上面两步,相关的编译配置结果如下图:...

  4. Preprocessor Macros添加USE_SOT=1,后面用来控制是否编译调用SDK的代码...

  5. 因为SOT SDK库文件编译时不带Bitcode,所以也需要把Enable Bitcode设为No...

  6. 为了模拟器架构时不编译arm64,给SotRelease增加如下配置...或者把Build Active Architecture Only设为Yes

Step4: 增加拷贝补丁脚本

SDK里提供了一个便利脚本,路径在sdk目录的project-script/sot_package.sh,它会把生成的补丁拷贝到Bundle文件夹下,在每次项目编译成功时调用该脚本,添加步骤如下:

...

脚本内容为:


    if [[ "$CONFIGURATION" == "SotDebug" || "$CONFIGURATION" == "SotRelease" ]];then
      sh "$SOURCE_ROOT/sotsdk/project-script/sot_package.sh" "$SOURCE_ROOT/sotsdk/project-script/sotconfig.sh" "$SOURCE_ROOT/sotsaved/$CONFIGURATION" Demo
    fi
    

...

Based on dependency analysis的勾去掉。


Step5: 链接C++库

SOT需要压缩库和c++标准库的支持,还是在这个页面下,打开Link Binary With Libraries页...

点击加号,分别加入这两,libz.tbdlibc++.tbd...


Step6: 调用SDK API

需要用Swift代码调用OC代码,已经提供了一个样例代码在SDK的swift-call-objc目录中,先把callsot.h和callsot.m拷贝到Demo目录下...

再添加到Demo工程中。点击Xcode软件的File按钮,找到Demo目录下的callsot.h和callsot.m,接着点击Add Files to "Demo",如下图所示:...

点击Add按钮,添加后会弹出询问:是否创建桥接文件。点击按钮Create Bridging Header...

然后可以看到项目中多了3个文件,分别是callsot.h,callsot.m和Demo-Bridging-Header.h:...

打开Demo-Bridging-Header.h,加入一行代码#import "callsot.h"...

打开callsot.m,修改代码为

#import <Foundation/Foundation.h>
#import "callsot.h"
#import "../sotsdk/libs/SotWebService.h"
@implementation CallSot:NSObject
-(void) InitSot
{
#ifdef USE_SOT
#ifdef DEBUG
    [SotWebService ApplyBundleShip];
#else
    
   SotApplyCachedResult ApplyShipResult = [SotWebService ApplyCachedAndPullShip:@"1234567" is_dev:false cb:^(SotDownloadScriptStatus status)
    {
        if(status == SotScriptShipAlreadyNewest)
        {
            NSLog(@"SyncOnly SotScriptShipAlreadyNewest");
        }
        else if(status == SotScriptShipHasSyncNewer)
        {
            NSLog(@"SyncOnly SotScriptShipHasSyncNewer");
        }
        else if(status == SotScriptShipDisable)
        {
            NSLog(@"SyncOnly SotScriptShipDisable");
        }
        else
        {
            NSLog(@"SyncOnly SotScriptStatusFailure");
        }
    }];

    if(ApplyShipResult.Success)
    {
        if(ApplyShipResult.ShipMD5)
            NSLog(@"sot success apply cached ship md5:%@", ApplyShipResult.ShipMD5);
    }
    
#endif
#endif
}
@end

注意SotWebService.h的头文件路径不再依赖于绝对路径,并且代码里用了#ifdef USE_SOT宏来隔开API调用代码,不影响正常编译:

addfiles4.png 打开AppDelegate.swift,加入两行代码let sot = CallSot()sot.initSot(),重载init函数可以让SOT初始化的时机非常早,能够实现修改首屏代码的目的。

call_api5.png 注意:读者在应用到自己项目中时,以上这些配置的路径不要生搬硬套。例如找不到SotWebService.h文件,找不到sotconfig.sh文件等等,读者自己要清楚SDK的目录与自己工程目录的相对关系,灵活调整这些配置的路径。

测试热更-免费版

按上面配置完之后,先测试免费版热更功能

Step1: 热更注入

  1. Build Configuration切换到SotDebug...
  2. 确保sotconfig.sh的配置是,EnableSot=1以及GenerateSotShip=0,先Clean Build Folder一下,然后再Build:...

然后看编译日志的输出,Link日志可以看到run sot link等输出,会告诉你每个文件里哪些函数可以被热更等信息:......

项目编译成功了,该APP可以正常启动。同时它具备了热更能力,可以加载补丁改变程序的代码逻辑,下面介绍如何生成补丁来测试它。


Step2: 生成补丁

上一步进行了热更注入的编译,当时的代码保存到了Demo/sotsaved这个文件夹下,用来和新代码比较生成补丁。生成补丁步骤如下:

  1. 首先启动SOT生成补丁模式,修改sotconfig.shEnableSot=1GenerateSotShip=1
  2. ...
  3. 接下来直接在Xcode里修改源代码,把ViewController.swift文件的”Something is horribly wrong!“改成了”SOT is great“,修改前:...修改后:...
  4. 不需要Clean项目,可直接Build项目。然后查看编译日志输出,可以看到生成了补丁并且被脚本拷贝到了Bundle目录下,可以展开Link Demo(x86_64)的编译日志:...点击展开后,可看到生成补丁的Link日志,日志里显示了函数demoBasics被修改了:...
  5. 生成出来的补丁原始文件保存到了Demo/sotsaved/SotDebug/x86_64/ship/ship.sot,还记得之前加了一个script到Build Phase中吗?它会每次编译结束时,会把这个补丁拷贝到了Bundle目录中,并且添加CPU架构到文件名中。可以在Bundle中看到这个补丁,至此完毕。

Step3: 加载补丁

启动APP,API会判断Bundle内是否有补丁,有则加载,加载成功的日志大概如下,提示有一个模块加载了热更补丁:...之后点击最上面的MESSAGE VIEW控件,发现弹出的文案变成了SOT is great:...

如果去Xcode断点调试demoBasics,会发现无法断住了,因为实际执行补丁代码的是SOT虚拟机。

顺便提一嘴,GenerateSotShip=1时,编译APP用的是保存在sotsaved目录下的代码,所以无论怎么修改Xcode里的代码,如果没有把补丁拷贝到Bundle目录里,那么APP都是最后一次GenerateSotShip=0热更注入时的样子。

如果怀疑,可以把拷贝补丁的Script脚本从Build Phases删除,可以发现GenerateSotShip=1怎么改代码都不会生效了。

注意:如果读者接入自己的一个很简单项目进行测试,例如设置某个控件的颜色,热更前是红色,修改后是绿色,发现无法生效。那是因为这样的项目太过于简单,寥寥几行代码。热更前没有访问过绿色的这个全局变量,在热更时也无法访问到了,SOT只能利用原有的能力,无法无中生有。所以不要这样测试,更具体的原因在「 热更能力-语言特性 」说明。通常完整的项目代码比较多,所以就不会有这样的缺陷。

接入网站版

按上面的教程,已经对APP实现了免费版和网站版的接入。它俩区别只是链接的库不一样,具体就是Other Linker Flags根据Configuration区别配置,SotRelease下接入了网站版。但除了APP接入了网站版SDK,还需要用配合网站来管理补丁的发布。

Step1: 注册网站

  1. 第一步当然是注册网站,成为会员。点击跳转注册页面,免费注册,注册需要验证邮箱,然后登录。
  2. 从导航栏进入我的APP:...
  3. 点击创建APP,弹出弹窗填写APP的名字:...
  4. 进入APP页面,点击右上角的创建新版本按钮,会弹出弹窗,需要选择网站版,SDK版本选择1.0,目前只有1.0版本,然后输入版本号,版本号可以是随意字符串,方便区分就行。...
  5. 创建版本成功后,点击版本,进入版本页面,左上角是唯一标识该版本的VersionKey,后面API接口需要这个Key。...

Step2: 修改VersionKey

打开callsot.m,修改网站版的同步补丁接口,第一个参数填入你在网站创建的版本的VersionKey。至此,网站版热更就算接入完成了。

versionkey.png

Step3: 测试网站热更

  • 网站版生成补丁的步骤免费版是一样的,需要经历热更注入->出包->修改代码->生成补丁,这里不再赘述。

    唯一不同的是,生成出来的补丁要上传到网站上,然后才能通过网络同步到手机上实现热更。通过之前的免费版教程,知道生成的补丁会被拷贝到Bundle目录下,所以去Bundle目录里就能找它,在Xcode导航栏里右键选择Products下的Demo.app,选择Show in Finder:...

  • 右键Demo文件,选择Show Package Contents:...

  • 找到目录下的sotship_arm64.sot,这里用手机测试,cpu是arm64类型,补丁名字带有cpu后缀,这就是补丁了:...

  • 回到网站的版本页面,点击右侧上传补丁按钮:...

  • 弹出页面里,真机的架构一般选择arm64,除非是老的armv7的机器,并把补丁文件拖到框里,点击上传:...

  • 上传成功并且补丁文件无异常(补丁最大支持5MB),则会添加成功,补丁默认是停用状态,需要点击编辑来启用它:

web_sync3.png

  • 这里选择全量启用,点击下面的提交按钮,然后补丁就会成功启用了:...

  • 上一步更新了补丁状态,通常很快生效,但CDN有时也需要1到2分钟才能生效。之后手机打开APP,第一次打开的话本地没有缓存补丁,热更是不生效的,能看到下面的日志:web_sync1.png 这里输出的md5也跟网站上的补丁md5是一致的。

  • 关闭APP再次打开,发现日志变了,加载了上次打开时缓存下来的补丁了。...这里输出的md5也跟网站上的补丁md5是一致的。

  • 打开APP后,点击最上面的MESSAGE VIEW控件,发现弹出的文案变成了SOT is great:...

注意:使用网站版,需要考虑到网络传输延迟的问题,只有看到了下载补丁和成功加载补丁的日志之后,调用的函数才会使用热修后的函数。使用ApplyCachedAndPullShip这个接口的话,第一次打开APP是不会加载到最新补丁的,下次打开才会加载上次缓存到的补丁。具体时序问题请看「 同步补丁API 」解释。

构建热更注入版本和构建补丁必须是同一台机器,同一个Xcode版本和同一个SOT SDK版本。例如上架前APP用Xcode12进行了热更注入,而之后用Xcode13来构建补丁,那么将得到无效甚至错误的补丁。请使用同一个版本Xcode。

Step4: 几点提示

  • 网站版跟免费版主要接入流程差不多,可以用免费版测试,功能通过测试之后再接入网站版。
  • 网站版费用很低,日活1万的APP,一个月几十块就够了。
  • 网站版补丁和配置都放在CDN上,支持高并发。

非主Target接入热更

上面的教程都是针对主Target,也就是Demo。这个工程还有一个名为SwiftMessages的Framework,也可以热更,下面介绍如何配置。

可以看到SwiftMessages的Mach-O Type是Dynamic Library,通过下图方式查看得到:...

这种类型的话,配置相对麻烦些。还有一种是Static Library,配置起来会简单得多。但本例改成Static Library启动会崩溃,所以按Dynamic Library的方式来介绍。

Step1: 修改编译选项

  1. 选中SwiftMessages.xcodeproject工程,然后选择SwiftMessages这个Target,再选择Build Settings:...
  2. Other Linker FlagsSotDebug中添加-sotmodule $(PRODUCT_NAME) $(SRCROOT)/Demo/sotsdk/libs/libsot_free.a -sotsaved $(SRCROOT)/Demo/sotsaved/$(CONFIGURATION)/$(CURRENT_ARCH) -sotconfig $(SRCROOT)/Demo/sotsdk/project-script/sotconfig.sh
  3. Other Linker FlagsSotRelease中添加-sotmodule $(PRODUCT_NAME) $(SRCROOT)/Demo/sotsdk/libs/libsot_web.a -sotsaved $(SRCROOT)/Demo/sotsaved/$(CONFIGURATION)/$(CURRENT_ARCH) -sotconfig $(SRCROOT)/Demo/sotsdk/project-script/sotconfig.sh
  4. Other C Flags以及Other Swift FlagsSotDebugSotRelease中,添加-sotmodule $(PRODUCT_NAME) -sotconfig $(SRCROOT)/Demo/sotsdk/project-script/sotconfig.sh,意义跟上一步是一样的,需要保持一致。经过上面两步,相关的编译配置结果如下图:...这一步跟Demo的配置差不多,区别在于有些路径写法不一样,以达到复用Demo配置的目的,读者可以仔细比较一下。
  5. Preprocessor Macros添加USE_SOT=1,后面用来控制是否编译调用SDK的代码...
  6. 需要把Target的Enable Bitcode设为No...
  7. 为了模拟器架构时不编译arm64,给SotRelease增加如下配置...

Step2: 链接C++库

点击Build Phases页面,打开Link Binary With Libraries页,点击加号,分别加入这两,libz.tbdlibc++.tbd...

Step3: 调用SDK API

因为SwiftMessages是动态库,所以需要在它的编译文件中调用SDK的热更初始化接口。跟Demo一样,添加OC文件。先从Demo文件夹中复制callsot.h和callsot.m文件到SwiftMessages文件夹中...

选中SwiftMessages工程,点击Xcode软件的File按钮,接着点击Add Files to "SwiftMessages.xcodeproject",如下图所示:...

选择到SwiftMessages目录,同时选中callsot.h和callsot.m两个文件,勾选下面的Copy items if needed,勾选Add to targets:中的SwiftMessages target,如下图所示:...

点击Add按钮,然后可以看到项目中多了2个文件,分别是callsot.h,callsot.m,修改CallSot类名为CallSotMessage:...

去到右边面板,把文件属性改成public:...

打开callsot.m,做相应路径和类名的修改:...

打开Demo-Bridging-Header.h,加入一行代码#import "SwiftMessages/callsot.h"...

打开AppDelegate.swift,加入两行代码let sot1 = CallSotMessage()sot1.initSot()

third_addfiles7.png 因为这时候有两个Target都可以生成补丁,Demo和SwiftMessages,需要修改拷贝补丁的脚本,加入SwiftMessages:...

Step4: 测试热更

  1. 测试热更的流程跟之前是一模一样的,只是输出的日志可能会有所区别,我们过一遍。EnableSot=1和GenerateSotShip=0热更注入,先Clean后Build,如果去看编译日志的Link SwiftMessages,也可以看到热更注入的信息。

  2. 然后修改MessageView.swift的代码,错误提示的文案会加上“SOT is great”:...

  3. GenerateSotShip=1开启生成补丁模式,直接Build,查看Link SwiftMessages日志,有提示该函数被热更:...

  4. 接下来可以看到补丁拷贝脚本日志输出的信息,这里它检测到有两个Target都生成了补丁文件,会把它们两个合成一个,拷贝到Bundle目录下:...

  5. 启动APP,会看到两条加载补丁的日志,因为我们Demo Target和SwiftMessages Target都调用了API接口:...

  6. 点击MESSAGE VIEW控件,可以看到错误提示文案后面多了“SOT is great”,热更成功:

    ...网站版的测试跟以前也是一样的,这里不再重复了。

Step5: 几点提示

  1. Dynamic Library的热更编译改法其实跟主Target,也就是Mach-O Type为Executable的改法是一样,只是这里复用了主Target的一些配置,例如sotsaved目录和sotconfig.sh的路径。增加再多的Target也可以按同样的改法修改它们。
  2. 补丁拷贝脚本只需要主Target有就行了,把要热更的sotmodule对应的名字加上即可,条件就是sotsaved目录必须是同一个。
  3. 如果需要接入网站版,那么每个需要热更的Target都需要调用API跟网站同步,它们的消耗是独立计费的。

Static Library的改法

上面说到Dynamic Library的改法步骤比较多,而且有诸多缺点,如果能把Framework的Mach-O Type改成Static Library是最好的,会少很多步骤和配置。由于本例无法修改,这里简单说一下步骤:

  1. Other Libraian Flags添加-sotmodule $(PRODUCT_NAME) -sotsaved $(SRCROOT)/Demo/sotsaved/$(CONFIGURATION)/$(CURRENT_ARCH) -sotconfig $(SRCROOT)/Demo/sotsdk/project-script/sotconfig.sh ,注意是Other Libraian Flags而不是Other Linker Flags了。还有这里比Dynamic Library加的配置少一个,即没有链接SDK的.a库文件了。
  2. Other C Flags以及Other Swift Flags添加-sotmodule $(PRODUCT_NAME) -sotconfig $(SRCROOT)/Demo/sotsdk/project-script/sotconfig.sh,这步跟之前是一模一样的。
  3. 需要把Target的Enable Bitcode设为No
  4. 修改拷贝补丁的脚本,加入该Target的名字,例如本例加入SwiftMessages,跟之前也是一模一样的:...

然后就配置完了,如果是使用网站版,同步一次消耗,就能实现所有Target的热更,修改简单,对包体影响最小。


总结

本文完整介绍Swift项目如何接入免费版和网站版。

本文的方式是把SDK拷贝到了工程文件夹里,让它可以跟随项目一起进行版本管理,路径也配置成了相对路径,更加灵活。

通过新增Configuration的方式,也做到了不影响原来的开发,Debug和Release相当于没有接入SOT,适合大多数开发平时使用。只需上线前改成SotRelease出包,就能让APP就得到热更能力。