iOS 热更新解读(一)APatch & JavaScriptCore

简介: # iOS 动态更新的几种方案 - WebView 加载 HTML5 动态更新。 - React Native/weex js 动态更新。 - lua 脚本文件控制动态更新(代表框架 [WaxPatch](https://github.com/mmin18/WaxPatch) )。 - js

iOS 动态更新的几种方案

其中 WaxPatch 和 JSPatch 是使用较广泛的两种热修复方案。而苹果 review guideline 提到只允通过JavaScriptCore.frameworkWebKit执行脚本,因此 JSPatch 是真正被 Apple 官方支持的。此外鉴于JavaScriptlua语言更亲民,使用系统内置的 JavaScriptCore.framework而无需内嵌lua脚本引擎来解释运行lua代码,JSPatch 便成为目前 iOS 热修复使用最多,效果也最佳的方案。

有关上述几种热修复方案的比较可阅读这两篇文章:

Weex & ReactNative & JSPatch

APatch 与 JSPatch 的关系

JSPatch 使用时需要一个后台下发和管理脚本。阿里百川 HotFix 平台帮助开发者做了这些事。通过提供脚本托管、版本管理、脚本文件及传输过程加密等服务,让开发者无需搭建后台和关心部署操作,只需引入一个 SDK 即可直接使用 JSPatch 进行热修复。这个 SDK 就是 APatch(iOS)。

APatch 在 JSPatch 核心代码的基础上封装了向 HotFix 平台请求脚本/传输解密/脚本管理/本地调试等功能,是配合阿里百川 HotFix 平台一起使用的。

APatch 工作流程


JSPatch 脚本执行权限很高,若被第三方篡改会带来很大安全问题。因此 APatch 和 HotFix 平台都对安全问题考虑良多。

传输安全

从上图可看出,客户端从服务器下载 Patch 之前先要下载指定 Patch 配置信息即PatchInfo,其中包含了 Patch 文件密钥 file_token
服务端:

  • file_token 用 RSA 公钥加密。
  • PatchInfo 原始数据采用 HMacSha1 算法计算的哈希值,并将原始数据和哈希值serviceToken放在同一消息中传送给客户端。

客户端:

  • 使用 secret 计算所接收数据的哈希值。
  • 检查计算所得的 HMAC 是否与传送的 HMAC 匹配。
  • 只有PatchInfo通过校验匹配后才会去下载Patch

另外,update patch 的接口已迁至 https,进一步保证了数据传输的安全。

本地存储

本地存储的脚本被篡改的机会小很多,只在越狱机器上有点风险,对此 APatch SDK 对下载的脚本进行了AES对称加密,每次读取时:

  • 客户端使用 RSA 私钥解密 PatchInfo.file_token 获取 keyiv
  • 使用 keyiv 进行 AES 解密。

解密成功后的数据存储在 script 中,然后会调用 JSPatch 运行js脚本的接口:

[JPEngine evaluateScript:script];

至此,APatch 的工作已经完成,接下来具体的热修复工作就交给 JSPatch 了。

JSPatch —— 基于 JavaScriptCore.framework

JSPatch 是一个开源项目(Github链接),只需要在项目里引入极小的引擎文件,就可以使用 JavaScript 调用任何 Objective-C 的原生接口,替换任意 Objective-C 原生方法。目前主要用于下发 JS 脚本替换原生 Objective-C 代码,实时修复线上 bug。

——JSPatch wiki

“极小的引擎文件”指的就是 JavaScriptCore。OS X Mavericks 和 iOS 7 引入了 JavaScriptCore 库,它把 WebKit 的 JavaScript 引擎用 Objective-C 封装,提供了简单、快速、安全的方式接入世界上最流行的语言:

  • 在 Objective-C 代码中直接执行 JavaScript 代码段;
  • 在 JavaScript 语言环境里调用 Objective-C 公开给 JavaScript的 方法;
  • 内存管理和线程封装。

如果未接触过 JavaScriptCore,在深入学习 JSPatch 之前有必要先了解一下这个js引擎怎么使用。

OC 调用 JS

JSContext 是运行 JavaScript 代码的环境。可以在 JSContext中创建变量、计算、定义方法等:

JSContext *context = [[JSContext alloc] init];
[context evaluateScript:@"var num = 5 + 5"];
[context evaluateScript:@"var names = ['Grace', 'Ada', 'Margaret']"];
[context evaluateScript:@"var triple = function(value) { return value * 3 }"];

JSValue包装了每一个可能的 JavaScript 值,任何出自 JSContext 的值都被包裹在一个 JSValue 对象中:

JSValue *tripleNum = [context evaluateScript:@"triple(num)"];
//取出jsvalue中的值
NSLog(@"Tripled: %d", [tripleNum toInt32]);//30

JSContextJSValue 实例使用下标可以访问之前创建的 context 的任何值。JSContext 需要一个字符串下标,JSValue 使用字符串或整数下标来得到里面的对象和数组:

JSValue *names = context[@"names"];
JSValue *initialName = names[0];
NSLog(@"The first name: %@", [initialName toString]);//Grace

调用JS方法需要使用callWithArguments:传递参数:

JSValue *tripleFunction = context[@"triple"];
JSValue *result = [tripleFunction callWithArguments:@[@5]];
NSLog(@"five tripled:%d",[result toInt32]);

这里使用 Foundation 类型NSArray作为参数来直接调用该函数。JavaScriptCore 可以 很轻松地处理这个桥接。
以上js代码都以字符串形式直接出现在oc代码中,实际中也可以在项目中引入.js文件,执行js文件中的内容。即:

NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"main" ofType:@"js"];
NSString *jsCore = [[NSString alloc] initWithData:[[NSFileManager defaultManager] contentsAtPath:path] encoding:NSUTF8StringEncoding];
[context evaluateScript:jsCore];

JS调用OC

从JS访问在OC中定义的对象和方法有两种方式:

方式一: JSContext注册NSBlock对象:

context[@"add"] = ^(NSInteger a, NSInteger b) {
            NSLog(@"add result:%@", @(a + b));
        };
context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
            NSLog(@"%@", exception);
            con.exception = exception;
            //异常处理...
        };
[context evaluateScript:@"add(2,3)"];//5

方式二:OC对象实现JSExport协议:

定义一个Test类,遵循 JSExport协议:

// in Test.h -----------------

//定义一个JSExport子协议,暴露OC方法定义
@protocol TestJSExports <JSExport>

- (void)log:(id)value;
- (void)addX:(int)x withY:(int)y;

@end

@interface Test : NSObject <TestJSExports>

- (void)callOC;

@end

// in Test.m -----------------

@implementation Test

- (void)callOC {
    JSContext *context = [[JSContext alloc] init];
    //将实现了上面定义的协议的对象设置给JSContext
    context[@"Test"] = self;
    //执行在JSContext中的JS代码,即可以执行传入的对象的JSExport协议中定义的方法
    [context evaluateScript:@"Test.log('Hello JavaScript')"];
    [context evaluateScript:@"Test.addXWithY(1, 2);"];
}

- (void)log:(id)value {
    NSLog(@"value = %@", value);
}

- (void)addX:(int)x withY:(int)y {
    NSLog(@"x + y = %d", x + y);
}

@end

测试:

Test *test = [[Test alloc]init];
[test callOC];
//"value = Hello JavaScript","x + y = 3"

相关文章:

iOS 热更新解读(二)—— JSPatch 源码解析
iOS热更新解读(三)—— JSPatch 之于 Swift

目录
相关文章
|
1月前
|
运维 监控 安全
应用研发平台EMAS常见问题之sophix ios flutter热更新如何解决
应用研发平台EMAS(Enterprise Mobile Application Service)是阿里云提供的一个全栈移动应用开发平台,集成了应用开发、测试、部署、监控和运营服务;本合集旨在总结EMAS产品在应用开发和运维过程中的常见问题及解决方案,助力开发者和企业高效解决技术难题,加速移动应用的上线和稳定运行。
73 0
|
JavaScript 前端开发 小程序
IOS 版小程序:能让 JS 执行的 JavascriptCore ,到底是什么
Swift 自 2014 年推出以来,人气飙升,但是 JavaScript 是一种与 Swift 完全相反的语言,比如 Swift 在编译时做了很多保障安全性的措施,而 JavaScript 则是一门弱类型语言,它只在执行时运行。可能它们两个也没想到有一天能够一起协作,制作一个流畅的 iOS 应用程序!
2280 0
IOS 版小程序:能让 JS 执行的 JavascriptCore ,到底是什么
|
移动开发 开发框架 JavaScript
让你的iOS应用程序支持运行JavaScript脚本:JavaScriptCore框架详解(四)
让你的iOS应用程序支持运行JavaScript脚本:JavaScriptCore框架详解
477 0
让你的iOS应用程序支持运行JavaScript脚本:JavaScriptCore框架详解(四)
|
JSON JavaScript 前端开发
让你的iOS应用程序支持运行JavaScript脚本:JavaScriptCore框架详解(三)
让你的iOS应用程序支持运行JavaScript脚本:JavaScriptCore框架详解
480 0
|
JavaScript 前端开发 Java
让你的iOS应用程序支持运行JavaScript脚本:JavaScriptCore框架详解(二)
让你的iOS应用程序支持运行JavaScript脚本:JavaScriptCore框架详解
300 0
|
Web App开发 JavaScript 前端开发
让你的iOS应用程序支持运行JavaScript脚本:JavaScriptCore框架详解(一)
让你的iOS应用程序支持运行JavaScript脚本:JavaScriptCore框架详解
1014 0
让你的iOS应用程序支持运行JavaScript脚本:JavaScriptCore框架详解(一)
|
Android开发 iOS开发 数据格式
Ionic实现iOS与Android端代码『热更新』
热更新的好处 通常ionic源码可包括(HTML,JavaScript,CSS文件和其他资源),往常我们必须通过提交程序到应用市场,经过漫长的审核后才可让用户更新,每改动一个小地方都需要重新打新版本。
2371 0
|
JavaScript 前端开发 API
iOS 热更新解读(二)—— JSPatch 源码解析
关于 JSPatch 的实现原理,JSPatch 作者本人 [bang](http://blog.cnbang.net/) 已经有一系列文章阐述: - [JSPatch 实现原理详解 核心](http://mp.weixin.qq.com/s?__biz=MzIzNTQ2MDg2Ng==&mid
6470 0
|
JavaScript 前端开发 iOS开发
iOS热更新解读(三)—— JSPatch 之于 Swift
# 继承自 NSObject 的 Swift 类 ## 修改属性 新建 Swift 工程 `SwiftJSPatch`。 `AppDelegate.swift`: ```Swift // in AppDelegate.swift ---------------- func applica
7268 0