Mobile Development 27 min read

How JSPatch Enables Dynamic iOS Updates: Deep Dive into Its Runtime Mechanics

JSPatch is an open‑source iOS dynamic‑update framework that leverages Objective‑C runtime and JavaScriptCore to let developers call and replace native methods via JavaScript, with detailed explanations of its core principles, method invocation, replacement, struct and C‑function support, and advanced implementation tricks.

WeChat Client Technology Team
WeChat Client Technology Team
WeChat Client Technology Team
How JSPatch Enables Dynamic iOS Updates: Deep Dive into Its Runtime Mechanics

JSPatch is an iOS dynamic update framework that, by embedding a tiny engine, allows JavaScript to invoke any Objective‑C native API, providing the flexibility of a scripting language for adding modules or hot‑fixing bugs. The framework is open‑source on GitHub and is used by the WeChat iOS client.

Outline

基础原理

方法调用
  1.require
  2.JS接口
    i.封装 JS 对象
    ii.__c()元函数
  3.消息传递
  4.对象持有/转换
  5.类型转换

方法替换
  1.基础原理
  2.va_list实现(32位)
  3.ForwardInvocation实现(64位)
  4.新增方法
    i.方案
    ii.Protocol
  5.Property实现
  6.self关键字
  7.super关键字

扩展
  1.Struct 支持
  2.C 函数支持

总结

Basic Principles

JSPatch works because Objective‑C is a dynamic language; all method calls and class creation go through the Objective‑C Runtime at runtime. Classes and selectors can be obtained via NSClassFromString and NSSelectorFromString, enabling JavaScript to call or replace any method.

Class class = NSClassFromString("UIViewController");
id viewController = [[class alloc] init];
SEL selector = NSSelectorFromString("viewDidLoad");
[viewController performSelector:selector];

Methods can also be replaced:

static void newViewDidLoad(id slf, SEL sel) {}
class_replaceMethod(class, selector, newViewDidLoad, "");

New classes can be registered and methods added dynamically using objc_allocateClassPair, objc_registerClassPair, and class_addMethod.

Method Invocation

After requiring a class, JavaScript can create instances and call methods:

require('UIView')
var view = UIView.alloc().init()
view.setBackgroundColor(require('UIColor').grayColor())
view.setAlpha(0.5)

The require function creates a global variable that represents the Objective‑C class, storing __isCls and __clsName. Each method name becomes a JavaScript function that forwards calls to Objective‑C via a helper _methodFunc.

var _require = function(clsName) {
  if (!global[clsName]) {
    global[clsName] = { __isCls: 1, __clsName: clsName };
  }
  return global[clsName];
};

To avoid loading every inherited method into each JavaScript object (which would explode memory), JSPatch stores a single copy of each method and follows the inheritance chain at call time.

__c() Meta‑function

When a method is not defined in JavaScript, JSPatch rewrites the call to __c(), which forwards the selector and arguments to Objective‑C, mimicking Objective‑C/Lua/Ruby message forwarding.

UIView.alloc().init()
→
UIView.__c('alloc')().__c('init')()
Object.prototype.__c = function(methodName) {
  if (!this.__obj && !this.__clsName) return this[methodName].bind(this);
  var self = this;
  return function(){
    var args = Array.prototype.slice.call(arguments);
    return _methodFunc(self.__obj, self.__clsName, methodName, args, self.__isSuper);
  };
};

Message Passing

JSPatch creates a JSContext and registers native functions that JavaScript can call. Example:

JSContext *context = [[JSContext alloc] init];
context[@"hello"] = ^(NSString *msg) {
    NSLog(@"hello %@", msg);
};
[context evaluateScript:@"hello('world')"]; // prints "hello world"

JavaScriptCore automatically converts between Objective‑C collections, strings, numbers, and blocks.

Object Retention & Conversion

When an Objective‑C object is passed to JavaScript, its pointer is wrapped in an NSDictionary with the key __obj. JavaScript holds a reference, increasing the object's retain count; when the JavaScript reference is released, the retain count is decremented.

static NSDictionary *_wrapObj(id obj) {
    return @{ @"__obj": obj };
}

Type Conversion

Parameters are converted based on the method signature obtained from NSMethodSignature. Numbers become NSNumber, structs like CGRect are specially handled, and the return value is wrapped back for JavaScript.

Method Replacement

Using class_replaceMethod, JSPatch can replace any selector with a custom IMP that calls back into JavaScript. The original implementation is saved under a new selector (e.g., ORIGviewDidLoad) so it can be invoked later.

static void viewDidLoadIMP (id slf, SEL sel) {
   JSValue *jsFunction = …;
   [jsFunction callWithArguments:nil];
}
Class cls = NSClassFromString(@"UIViewController");
SEL selector = @selector(viewDidLoad);
Method method = class_getInstanceMethod(cls, selector);
IMP imp = method_getImplementation(method);
char *typeDescription = (char *)method_getTypeEncoding(method);
class_addMethod(cls, @selector(ORIGviewDidLoad), imp, typeDescription);
class_replaceMethod(cls, selector, viewDidLoadIMP, typeDescription);

For methods with parameters, a generic IMP uses va_list on 32‑bit devices or forwardInvocation: on 64‑bit devices to capture all arguments and forward them to JavaScript.

va_list Implementation (32‑bit)

static void commonIMP(id slf, ...) {
  va_list args; va_start(args, slf);
  NSMutableArray *list = [NSMutableArray array];
  NSMethodSignature *sig = [cls instanceMethodSignatureForSelector:selector];
  for (NSUInteger i = 2; i < sig.numberOfArguments; i++) {
    const char *type = [sig getArgumentTypeAtIndex:i];
    // convert based on type character
    ...
    [list addObject:obj];
  }
  va_end(args);
  [function callWithArguments:list];
}

ForwardInvocation Implementation (64‑bit)

By replacing a selector with _objc_msgForward, the call lands in -forwardInvocation:, where an NSInvocation provides full access to arguments. JSPatch extracts them, calls the JavaScript implementation, and optionally forwards to the original IMP.

Adding New Methods

JSPatch’s defineClass can add methods whose signatures are all id. For protocols with non‑id parameters, the protocol is parsed and the correct types are used.

Property Support

Properties are accessed via generated getter/setter methods. Dynamic properties are simulated using associated objects ( objc_getAssociatedObject / objc_setAssociatedObject).

self and super Keywords

The global self variable is set to the current Objective‑C instance before a JavaScript method runs, allowing self to be used inside the JS body. Calling self.super() creates a proxy object with __isSuper = 1; the native bridge then invokes the superclass implementation.

Extensions

Struct Support

Beyond the built‑in structs, developers can define new structs at runtime by providing a name, type string, and field keys. JSPatch then packs/unpacks the memory layout when passing between JS and Objective‑C.

require('JPEngine').defineStruct({
  "name": "JPDemoStruct",
  "types": "FldB",
  "keys": ["a", "b", "c", "d"]
});

C Function Support

C functions are exposed to JavaScript by adding them to the JSContext. For example, memcpy can be called directly from JS after being wrapped.

context[@"memcpy"] = ^(JSValue *des, JSValue *src, size_t n) {
    memcpy([self formatPointerJSToOC:des], [self formatPointerJSToOC:src], n);
};

Extensions can be loaded on demand via require('JPEngine').addExtensions(['JPMemory']), keeping the core engine lightweight.

Summary

JSPatch leverages the dynamic nature of Objective‑C and JavaScriptCore to provide a powerful iOS hot‑fix solution. It implements class and method discovery, method replacement, property handling, struct and C‑function bridging, and advanced features like self / super emulation, all while managing memory and type conversion transparently.

For the full source and additional details, see the GitHub repository: https://github.com/bang590/JSPatch

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

iOSRuntimeJavaScriptCoredynamic updateJSPatch
WeChat Client Technology Team
Written by

WeChat Client Technology Team

Official account of the WeChat mobile client development team, sharing development experience, cutting‑edge tech, and little‑known stories across Android, iOS, macOS, Windows Phone, and Windows.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.