1
1
import _CJavaScriptKit
2
2
3
- fileprivate var closureRef : JavaScriptHostFuncRef = 0
4
- fileprivate var sharedClosures : [ JavaScriptHostFuncRef : ( [ JSValue ] ) -> JSValue ] = [ : ]
5
-
6
- /// JSClosureProtocol abstracts closure object in JavaScript, whose lifetime is manualy managed
3
+ /// JSClosureProtocol abstracts closure object in JavaScript, whose lifetime might be manualy managed
7
4
public protocol JSClosureProtocol : JSValueCompatible {
8
5
9
6
/// Release this function resource.
@@ -28,8 +25,15 @@ public protocol JSClosureProtocol: JSValueCompatible {
28
25
/// ```
29
26
///
30
27
public class JSClosure : JSObject , JSClosureProtocol {
28
+
29
+ fileprivate static var sharedClosures : [ JavaScriptHostFuncRef : ( [ JSValue ] ) -> JSValue ] = [ : ]
30
+
31
31
private var hostFuncRef : JavaScriptHostFuncRef = 0
32
32
33
+ #if JAVASCRIPTKIT_WITHOUT_WEAKREFS
34
+ private var isReleased : Bool = false
35
+ #endif
36
+
33
37
@available ( * , deprecated, message: " This initializer will be removed in the next minor version update. Please use `init(_ body: @escaping ([JSValue]) -> JSValue)` and add `return .undefined` to the end of your closure " )
34
38
@_disfavoredOverload
35
39
public convenience init ( _ body: @escaping ( [ JSValue ] ) -> ( ) ) {
@@ -40,26 +44,27 @@ public class JSClosure: JSObject, JSClosureProtocol {
40
44
}
41
45
42
46
public init ( _ body: @escaping ( [ JSValue ] ) -> JSValue ) {
43
- self . hostFuncRef = closureRef
44
- closureRef += 1
45
-
46
- // Retain the given body in static storage by `closureRef`.
47
- sharedClosures [ self . hostFuncRef ] = body
48
-
49
- // Create a new JavaScript function which calls the given Swift function.
47
+ // 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`.
48
+ super . init ( id : 0 )
49
+ let objectId = ObjectIdentifier ( self )
50
+ let funcRef = JavaScriptHostFuncRef ( bitPattern : Int32 ( objectId . hashValue ) )
51
+ // 2. Retain the given body in static storage by `funcRef`.
52
+ Self . sharedClosures [ funcRef ] = body
53
+ // 3. Create a new JavaScript function which calls the given Swift function.
50
54
var objectRef : JavaScriptObjectRef = 0
51
- _create_function ( self . hostFuncRef , & objectRef)
55
+ _create_function ( funcRef , & objectRef)
52
56
53
- super. init ( id: objectRef)
57
+ hostFuncRef = funcRef
58
+ id = objectRef
54
59
}
55
60
56
- @ available ( * , deprecated , message : " JSClosure.release() is no longer necessary " )
57
- public func release ( ) { }
58
- }
59
-
60
- @ _cdecl ( " _free_host_function_impl " )
61
- func _free_host_function_impl ( _ hostFuncRef : JavaScriptHostFuncRef ) {
62
- sharedClosures [ hostFuncRef ] = nil
61
+ #if JAVASCRIPTKIT_WITHOUT_WEAKREFS
62
+ deinit {
63
+ guard isReleased else {
64
+ fatalError ( " release() must be called on JSClosure objects manually before they are deallocated " )
65
+ }
66
+ }
67
+ #endif
63
68
}
64
69
65
70
@@ -103,7 +108,7 @@ func _call_host_function_impl(
103
108
_ argv: UnsafePointer < RawJSValue > , _ argc: Int32 ,
104
109
_ callbackFuncRef: JavaScriptObjectRef
105
110
) {
106
- guard let hostFunc = sharedClosures [ hostFuncRef] else {
111
+ guard let hostFunc = JSClosure . sharedClosures [ hostFuncRef] else {
107
112
fatalError ( " The function was already released " )
108
113
}
109
114
let arguments = UnsafeBufferPointer ( start: argv, count: Int ( argc) ) . map {
@@ -114,10 +119,16 @@ func _call_host_function_impl(
114
119
_ = callbackFuncRef ( result)
115
120
}
116
121
122
+
123
+
124
+ // [WeakRefs](https://github.com/tc39/proposal-weakrefs) is already Stage 4, but it's still newish API,
125
+ // so provide a escape hatch.
126
+ // Please build with `-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS` by SwiftPM build system
127
+ #if JAVASCRIPTKIT_WITHOUT_WEAKREFS
128
+
117
129
// MARK: - Legacy Closure Types
118
130
119
131
/// `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
132
public class JSOneshotClosure : JSObject , JSClosureProtocol {
122
133
private var hostFuncRef : JavaScriptHostFuncRef = 0
123
134
@@ -127,7 +138,7 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol {
127
138
let objectId = ObjectIdentifier ( self )
128
139
let funcRef = JavaScriptHostFuncRef ( bitPattern: Int32 ( objectId. hashValue) )
129
140
// 2. Retain the given body in static storage by `funcRef`.
130
- sharedClosures [ funcRef] = {
141
+ JSClosure . sharedClosures [ funcRef] = {
131
142
defer { self . release ( ) }
132
143
return body ( $0)
133
144
}
@@ -142,38 +153,31 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol {
142
153
/// Release this function resource.
143
154
/// After calling `release`, calling this function from JavaScript will fail.
144
155
public func release( ) {
145
- sharedClosures [ hostFuncRef] = nil
156
+ JSClosure . sharedClosures [ hostFuncRef] = nil
146
157
}
147
158
}
148
159
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
-
160
+ extension JSClosure {
168
161
public func release( ) {
169
162
isReleased = true
170
- sharedClosures [ hostFuncRef] = nil
163
+ Self . sharedClosures [ hostFuncRef] = nil
171
164
}
165
+ }
172
166
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
- }
167
+ #else
168
+
169
+ @available ( * , deprecated, renamed: " JSClosure " , message: " JSClosure supports automatic memory management " )
170
+ public typealias JSOneshotClosure = JSClosure
171
+
172
+ extension JSClosure {
173
+
174
+ @available ( * , deprecated, message: " JSClosure.release() is no longer necessary if the target environment supports WeakRefs " )
175
+ public func release( ) { }
176
+
177
+ }
178
+
179
+ @_cdecl ( " _free_host_function_impl " )
180
+ func _free_host_function_impl( _ hostFuncRef: JavaScriptHostFuncRef ) {
181
+ JSClosure . sharedClosures [ hostFuncRef] = nil
179
182
}
183
+ #endif
0 commit comments