初探JSPatch

作者 : 开心源码 本文共5570个字,预计阅读时间需要14分钟 发布时间: 2022-05-11 共82人阅读

前言

iOS平台的有很多热修复框架,原理都是差不多,都是利使用 Runtime 进行属性、方法修改。
JSPatch 是现今比较主流、轻量级的热修复框架。利使用内置的 JavaScript 引擎(JavaScriptCore)结合 JavaScript 在运行时进行对 Object-C 对象修改。

接入文档

JSPatch 的官方接入文档写的很详细,不过也很简洁。对于 Objective-C 项目已经足够用了但是对于 Swift 项目的接入介绍还是略显简略。目前,因为 Apple 公司对热修复的打压以及等等其余起因,使得 JSPatch 分为JSPatch平台版Github 的开源代码版

Github 的开源代码版:

# Your Podfileplatform :ios, '6.0'pod 'JSPatch'

JSPatch 平台版:
JSPatch 平台版只支持手动集成方式, 没有放到CocoaPods专门管理。

  1. JSPatchPlatform.framework 拖入项目中,勾选 “Copy items if needed”,并确保 “Add to target” 勾选了相应的 target。

  2. 增加依赖框架:TARGETS -> Build Phases -> Link Binary With Libraries -> + 增加 libz.dylibJavaScriptCore.framework

  3. 生成和配置RSA密钥。

openssl >genrsa -out rsa_private_key.pem 1024pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM –nocryptrsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
  1. 启动运行
#import <JSPatchPlatform/JSPatch.h>@implementation AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {   [JSPatch startWithAppKey:@"你的AppKey"];   [JSPatch setupRSAPublicKey:@"你的公钥"];   [JSPatch sync];   ...}@end

注意事项:
Swift 项目,因为 JSPatch 平台版因为 JSPatchPlatform.framework 里的 “Header”文件定义了与热修复类、方法相同的宏,导致 Swift 无法直接桥接

#define JSPatch Eb_tCode#define startWithAppKey stwa_43#define setupRSAPublicKey strs_3x#define setupTestScriptFileName sttsc_3#define updateConfigWithAppKey udcak#define testScriptInBundle tests_sinbund#define JPCallbackType jtspc_b#define JPErrorCode DRkcos#define setupCallback sefjtpsytecal

处理方法:
定义一个 Object-C 的桥接对象,进行桥接。

#import <JSPatchPlatform/JSPatch.h>@interface Patch : NSObject/**开始配置热修复 */+ (void)start;/** 同步补丁 */+ (void)sync;@end@implementation Patch+ (void)start {    [JSPatch startWithAppKey:appKey];    [JSPatch setupRSAPublicKey:@"你的公钥"];}+ (void)sync {    [JSPatch sync];}@end

桥接头文件导入 Patch.h,之后即可以在Swift中调使用:

class AppDelegate: UIResponder, UIApplicationDelegate {    func application(_ application: UIApplication, didFinishLaunchingWithOptionslaunchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {                Patch.start() //配置热修复        Patch.sync()  //同步下载补丁,这个方法可放在其余地方调使用        return true    }}

编写工具

JSPatch 编写工具体验上都不太好,一般编写和调试的工具都是分开。调试工具一般能调试JavaScript的浏览器就可。编写工具种类比较多,只需能友好的编写 JavaScript 的就行。

编写工具推荐

  • Sublime Text 轻量级文本编辑器
  • Atom 很多东西需要翻墙用
  • AppCode 重量级的IDE适合当做Xcode用

调试工具推荐

  • Safari浏览器
  • Google浏览器

基本用

JSPatch 基本用,官方文档也已经有详细说明。可以说学习 JSPatch 的门槛比较低,官网提供的少量工具方便并提升了开发效率,不过有一点需要注意的是不要太依赖官方的工具(只支持常规的语法,而且很容易出错),所以需要对脚本进行语法检查。本文主要补充少量 Swift 项目的用以及注意事项说明。

Objective-C 项目

JSPatch 尽管已经很方便对代码进行热修复,但是对少量的支持并不是很好,比方:

Struct 支持部分系统结构体,其余的需要在项目中和脚本中写
C 函数 用 JPCFunction 扩展支持
Block 用 JPBlock 扩展支持
GCD 用 JPDispatch 扩展支持
指针 用 JPMemory 扩展支持
常量、枚举、宏、全局变量 无法支持

参照 官方文档

Swift 项目

JSPatch 是利使用 Objective-CRuntime 进行改写、修改的;而 Swift 是利使用 C++ 的那一套静态机制,编译的时候已经决定了不能修改,所以 Swift 项目是不支持热修复的。为了让 Swift 项目也能支持热修复,所以需要把 Swift 使用到的类 进行桥接Objective-C 对应的对象,这样就能实现热修复了。

官方文档说明:

1.  只支持调使用继承自 NSObject 的 Swift 类2.  继承自 NSObject 的 Swift 类,其继承自父类的方法和属性可以在 JS 调使用,其余自己设置方法和属性同样需要加 @objc 和 dynamic 关键字才行。3.  若方法的参数/属性类型为 Swift 特有(如 Character / Tuple),则此方法和属性无法通过 JS 调使用。4.  Swift 项目在 JSPatch 新添加类与 OC 无异,可以正常用。

编写 JavaScript 脚本

因为 Swift 不能直接支持热修复,所以只能把需要修改的 Swift 语言写的类、属性、方法转成对应的 Objective-C 代码。一般编写脚本步骤:

1. 利使用Xcode混编项目,在 Objective-C 文件中用将要改变的 Swift 的代码。目的为了查看转成 Swift 对象转成 OC 对象的方法名。2. Swift 类名 = 项目名.类名3. 将替换的 OC 代码 -> JS 脚本

对于第二点,这里说明一下,比方我有一个项目 SwiftDemo 需要改写 TestProject 类下面的实例方法 testLog,就需要如下写:

defineClass("SwiftDemo.TestProject", {            testLog: function() {                console.log("打印 JS Log") //不能使用 NSLog('xx'),应该使用 console.log('xx')            }})

总结:
编写 JavaScript 脚本主要的转换流程 Swift -> Objective-C -> JavaScript
无法实现这条链路转换的都无法进行热修复

编写项目

为了能把 Swift 代码转换为 Objective-C 代码,需要对 Swift 代码进行一系列的修改。所以,本文对 Swift 代码定义少量规范:

  • Struct 结构体不能用,由于无法桥接成 OC 对象。无法拥有动态属性

  • 公告 Class 需要继承 NSObject,并且对属性和方法进行动态说明,也就是需要增加相应的 @objcdynamic@objcMembers 关键字。

  1. 属性修改值,只要要 @objc 就可
  2. JSPatch 调使用的方法只具备 @objc 就可,不需要 dynamic
  3. JSPatch 重写的方法需要具有 @objcdynamic 性质。

修改的 Swift 代码如下:

open class TestProject: NSObject {    @objc var pname: String = "原始名字" //不需要 dynamic 特性    @objc private var name: String = "原始名字" //不需要 dynamic 特性    @objc static var same: String = "原始名字" //不需要 dynamic 特性            public override init() {        super.init()    }        @objc func start() {        self.testLog()    }        @objc dynamic func testLog() {        //重写需要 @objc dynamic 性质        print("原始打印log")    }        @objc fileprivate func orgMethod() { //调使用的方法不使用 dynamic        print("原始orgMethod")        print("pname = \(self.pname)")        print("name = \(self.name)")        print("static same = \(DCTestProject.same)")        print("执行完成")    }    }@objcMembersopen class TestProject: NSObject {    var pname: String = "原始名字" //不需要 dynamic 特性    @objc private var name: String = "原始名字" //不需要 dynamic 特性    static var same: String = "原始名字" //不需要 dynamic 特性            public override init() {        super.init()    }        func start() {        self.testLog()    }        dynamic func testLog() {        //重写需要 @objc dynamic 性质        print("原始打印log")    }        @objc fileprivate func orgMethod() {         //调使用的方法不使用 dynamic, 但私有方法需要手动加 @objc        print("原始orgMethod")        print("pname = \(self.pname)")        print("name = \(self.name)")        print("static same = \(DCTestProject.same)")        print("执行完成")    }    }

JSPatch 脚本如下:

defineClass("SwiftDemo.TestProject", {            testLog: function() {                console.log("打印 JS Log");                self.setPname("打印 JS");                self.setName("打印 JS");                require('SwiftDemo.TestProject').setSame("打印 JS");                self.orgMethod();            }})
  • Enum 枚举尽量少使用,需要少量特殊解决,并且枚举中不能有其余方法。即便桥接成OC枚举,JavaScript没办法获取。
@objc public enum NVActivityIndicatorType: Int {     case Blank     case BallPulse     case BallGridPulse     case BallClipRotate     case SquareSpin  }  
  • Protocol 协议需要在相应的地方增加 @objc 关键字, 并且继承 NSObjectProtocol 协议。
@objc protocol TestDelegate: NSObjectProtocol {    @objc func TestClick(Str: String)}
  • 元组类型不能用。

  • 需要在 JavaScript 调使用或者者修改的方法都必需具备动态属性,而且方法所使用到的参数以及返回的对象都必需具备动态属性。

  • 调使用 C 函数 函数很麻烦需要做绑定操作,所以尽量少使用,而且不能保证所有的 C 函数 都能绑定调使用。尤其是内联函数

  • 常量、枚举、宏、全局变量不要用,由于 JavaScript 没办法获取。

  • 指针尽量不要用,对于 SwiftJavaScript 语言来说,指针用麻烦,容易出错。指针用方法请看JPMemory用文档

  • 方法里的代码尽量不能太多,尽量不要超过 30 行。对臃肿代码,尤其是逻辑比较重要的代码进行方法拆分。

  • 重写或者者调使用的方法的参数返回类型也必需需要能桥接到 Objective-C 代码中。

  • 项目中对于公使用工具类最好具有动态属性,而且假如是纯 Swift 写的就尽量中间封装动态中间类。

注意事项

说明一下 Swift 4.0 之后的两个修饰的关键字 @objc@objcMembers 比照:

  • Swift 4.0 之后的 @objcdynamic 关键字功能分开,也就是只增加 @objc 是不具备动态性的。
  • @objcMembers 会在类扩展子类的所有非 private 的方法和属性前增加 @objc 修饰,并且不会增加 dynamic 特性。

总结

热修复只是使用来线上紧急的 BugFix,没必要使用来做其余功能开发不必要的操作。对于 Swift 项目,还是平时注意一下代码编写逻辑,毕竟热修复针对的是 Objective-C 项目。

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 初探JSPatch

发表回复