Skip to content

Commit c77e069

Browse files
committed
Restore support for older runtimes using JSOneshotClosure + JSUnretainedClosure
1 parent 652eb14 commit c77e069

File tree

3 files changed

+69
-6
lines changed

3 files changed

+69
-6
lines changed

Runtime/src/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,11 @@ export class SwiftRuntime {
126126
constructor() {
127127
this.instance = null;
128128
this.heap = new SwiftRuntimeHeap();
129-
this.functionRegistry = new FinalizationRegistry(
130-
this.handleFree.bind(this)
131-
);
129+
if (typeof FinalizationRegistry !== "undefined") {
130+
this.functionRegistry = new FinalizationRegistry(
131+
this.handleFree.bind(this)
132+
);
133+
}
132134
}
133135

134136
handleFree(id: unknown) {

Sources/JavaScriptKit/Deprecated.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,3 @@ public typealias JSValueConstructible = ConstructibleFromJSValue
1515

1616
@available(*, deprecated, renamed: "JSValueCompatible")
1717
public typealias JSValueCodable = JSValueCompatible
18-
19-
@available(*, deprecated, renamed: "JSClosure")
20-
public typealias JSOneshotClosure = JSClosure

Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,67 @@ func _call_host_function_impl(
113113
let callbackFuncRef = JSFunction(id: callbackFuncRef)
114114
_ = callbackFuncRef(result)
115115
}
116+
117+
// MARK: - Legacy Closure Types
118+
119+
/// `JSOneshotClosure` is a JavaScript function that can be called only once.
120+
/// It is recommended to use `JSClosure` instead if your target runtimes support `FinalizationRegistry`.
121+
public class JSOneshotClosure: JSObject, JSClosureProtocol {
122+
private var hostFuncRef: JavaScriptHostFuncRef = 0
123+
124+
public init(_ body: @escaping ([JSValue]) -> JSValue) {
125+
// 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`.
126+
super.init(id: 0)
127+
let objectId = ObjectIdentifier(self)
128+
let funcRef = JavaScriptHostFuncRef(bitPattern: Int32(objectId.hashValue))
129+
// 2. Retain the given body in static storage by `funcRef`.
130+
sharedClosures[funcRef] = {
131+
defer { self.release() }
132+
return body($0)
133+
}
134+
// 3. Create a new JavaScript function which calls the given Swift function.
135+
var objectRef: JavaScriptObjectRef = 0
136+
_create_function(funcRef, &objectRef)
137+
138+
hostFuncRef = funcRef
139+
id = objectRef
140+
}
141+
142+
/// Release this function resource.
143+
/// After calling `release`, calling this function from JavaScript will fail.
144+
public func release() {
145+
sharedClosures[hostFuncRef] = nil
146+
}
147+
}
148+
149+
public class JSUnretainedClosure: JSObject, JSClosureProtocol {
150+
private var hostFuncRef: JavaScriptHostFuncRef = 0
151+
var isReleased: Bool = false
152+
153+
public init(_ body: @escaping ([JSValue]) -> JSValue) {
154+
// 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`.
155+
super.init(id: 0)
156+
let objectId = ObjectIdentifier(self)
157+
let funcRef = JavaScriptHostFuncRef(bitPattern: Int32(objectId.hashValue))
158+
// 2. Retain the given body in static storage by `funcRef`.
159+
sharedClosures[funcRef] = body
160+
// 3. Create a new JavaScript function which calls the given Swift function.
161+
var objectRef: JavaScriptObjectRef = 0
162+
_create_function(funcRef, &objectRef)
163+
164+
hostFuncRef = funcRef
165+
id = objectRef
166+
}
167+
168+
public func release() {
169+
isReleased = true
170+
sharedClosures[hostFuncRef] = nil
171+
}
172+
173+
deinit {
174+
guard isReleased else {
175+
// Safari doesn't support `FinalizationRegistry`, so we cannot automatically manage the lifetime of Swift objects
176+
fatalError("release() must be called on JSClosure objects manually before they are deallocated")
177+
}
178+
}
179+
}

0 commit comments

Comments
 (0)