expo-modules-jsi 57.0.4 → 57.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Date.swift +1 -1
- package/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptActor.swift +47 -28
- package/apple/Sources/ExpoModulesJSI-Cxx/include/RuntimeScheduler.h +2 -2
- package/apple/Tests/JavaScriptActorTests.swift +11 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,19 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 57.0.6 — 2026-08-26
|
|
14
|
+
|
|
15
|
+
### 💡 Others
|
|
16
|
+
|
|
17
|
+
- [iOS] `JavaScriptActor.assumeIsolated` no longer heap-allocates a closure box per call by keeping its `operation` non-escaping, making synchronous host calls ~1.6× faster. ([#47837](https://github.com/expo/expo/pull/47837) by [@tsapeta](https://github.com/tsapeta))
|
|
18
|
+
|
|
19
|
+
## 57.0.5 — 2026-08-20
|
|
20
|
+
|
|
21
|
+
### 🐛 Bug fixes
|
|
22
|
+
|
|
23
|
+
- [iOS] Fixed `dateFromMilliseconds` failing to compile with "type of expression is ambiguous" under newer toolchains: the unqualified `abs(_:)` in the `Double` overflow guard is ambiguous once C++ interop brings the C `abs` overloads into scope, so use `Double.magnitude` instead. ([#49039](https://github.com/expo/expo/pull/49039) by [@kraenhansen](https://github.com/kraenhansen))
|
|
24
|
+
- [iOS] Fixed the xcframework prebuild failing under Xcode 27 due to new foreign reference ownership warnings emitted for `RuntimeScheduler` constructors. ([#49120](https://github.com/expo/expo/pull/49120) by [@tsapeta](https://github.com/tsapeta))
|
|
25
|
+
|
|
13
26
|
## 57.0.4 — 2026-07-22
|
|
14
27
|
|
|
15
28
|
### 💡 Others
|
|
@@ -50,7 +50,7 @@ let maxJavaScriptDateMilliseconds: Double = 8_640_000_000_000_000
|
|
|
50
50
|
/// faithful to `new Date(number)`; the `Date`/string branches pass an already-clipped `getTime()` through.
|
|
51
51
|
@usableFromInline
|
|
52
52
|
func dateFromMilliseconds(_ milliseconds: Double) throws -> Date {
|
|
53
|
-
guard milliseconds.isFinite,
|
|
53
|
+
guard milliseconds.isFinite, milliseconds.magnitude <= maxJavaScriptDateMilliseconds else {
|
|
54
54
|
throw InvalidDateException()
|
|
55
55
|
}
|
|
56
56
|
return Date(timeIntervalSince1970: milliseconds.rounded(.towardZero) / 1000.0)
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
|
-
/// Name of the JavaScript thread created by React Native. Copied from `RCTJSThreadManager.mm`.
|
|
4
|
-
private let jsThreadName = "com.facebook.react.runtime.JavaScript"
|
|
5
|
-
|
|
6
3
|
/// Global actor that is used to isolate the code that should only be executed from the JavaScript thread.
|
|
7
4
|
/// Theoretically it does not act as a real actor; it uses a serial executor that executes jobs **synchronously**
|
|
8
5
|
/// without hopping to the proper thread. Meaning that running these jobs on the JavaScript thread must be ensured
|
|
@@ -23,25 +20,56 @@ public actor JavaScriptActor: GlobalActor {
|
|
|
23
20
|
/// An equivalent of `MainActor.assumeIsolated`, but for the `JavaScriptActor`. Assumes that the currently executing
|
|
24
21
|
/// synchronous function is actually executing on the JavaScript thread and invokes an isolated version of the operation,
|
|
25
22
|
/// allowing synchronous access to JavaScript runtime state without hopping through asynchronous boundaries.
|
|
26
|
-
///
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
typealias
|
|
23
|
+
/// The nonthrowing overload keeps `operation` nonescaping so its captured context can remain stack-allocated.
|
|
24
|
+
@_alwaysEmitIntoClient
|
|
25
|
+
@inline(__always)
|
|
26
|
+
public static func assumeIsolated<T: ~Copyable>(_ operation: @JavaScriptActor () -> T) -> T {
|
|
27
|
+
typealias IsolatedRunner = @JavaScriptActor (@JavaScriptActor () -> T) -> T
|
|
28
|
+
typealias NonisolatedRunner = (@JavaScriptActor () -> T) -> T
|
|
31
29
|
|
|
32
30
|
// This will crash if the current context cannot be isolated.
|
|
33
|
-
|
|
31
|
+
checkIsolated()
|
|
32
|
+
|
|
33
|
+
// Cast the capture-free runner rather than `operation` itself. `operation` remains nonescaping,
|
|
34
|
+
// so its captures can stay in the caller's stack frame.
|
|
35
|
+
let runner = unsafeBitCast(runIsolated as IsolatedRunner, to: NonisolatedRunner.self)
|
|
36
|
+
return runner(operation)
|
|
37
|
+
}
|
|
34
38
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
/// Throwing counterpart to the nonthrowing overload above. The generic error type keeps
|
|
40
|
+
/// `operation` nonescaping while preserving the exact error it can throw.
|
|
41
|
+
@_alwaysEmitIntoClient
|
|
42
|
+
@inline(__always)
|
|
43
|
+
public static func assumeIsolated<T: ~Copyable, E: Error>(
|
|
44
|
+
_ operation: @JavaScriptActor () throws(E) -> T
|
|
45
|
+
) throws(E) -> T {
|
|
46
|
+
// Casting a `throws(E)` function value requires newer OS runtime support. Wrapping the
|
|
47
|
+
// operation in the nonthrowing fast path keeps this backdeployable and stack-allocated.
|
|
48
|
+
let result: Result<T, E> = assumeIsolated {
|
|
49
|
+
return Result(catching: operation)
|
|
39
50
|
}
|
|
51
|
+
return try result.get()
|
|
40
52
|
}
|
|
41
53
|
|
|
42
|
-
///
|
|
54
|
+
/// In debug builds, asserts if the actor's executor is not isolating the current context.
|
|
55
|
+
@inlinable
|
|
56
|
+
@inline(__always)
|
|
43
57
|
public static func checkIsolated() {
|
|
44
|
-
|
|
58
|
+
// Using `assert` instead of `precondition` because this check is a heuristic based on
|
|
59
|
+
// thread name, not a precise isolation guarantee. Worklet runtimes legitimately run on
|
|
60
|
+
// the UI thread, which would cause a false-positive crash with `precondition`.
|
|
61
|
+
assert(
|
|
62
|
+
// JavaScript thread name copied from `RCTJSThreadManager.mm`.
|
|
63
|
+
Thread.current.name == "com.facebook.react.runtime.JavaScript" || !Thread.isMultiThreaded()
|
|
64
|
+
|| ProcessInfo.processInfo.processName == "xctest",
|
|
65
|
+
"JavaScriptActor operations must be run on the JavaScript thread"
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@JavaScriptActor
|
|
70
|
+
@usableFromInline
|
|
71
|
+
internal static func runIsolated<T: ~Copyable>(_ operation: @JavaScriptActor () -> T) -> T {
|
|
72
|
+
return operation()
|
|
45
73
|
}
|
|
46
74
|
}
|
|
47
75
|
|
|
@@ -57,20 +85,11 @@ internal class JavaScriptExecutor: SerialExecutor, @unchecked Sendable {
|
|
|
57
85
|
return UnownedSerialExecutor(ordinary: self)
|
|
58
86
|
}
|
|
59
87
|
|
|
60
|
-
///
|
|
88
|
+
/// Runtime hook used by Swift's actor data-race checks for synchronous entry into
|
|
89
|
+
/// `@JavaScriptActor` code. Do not remove: the `SerialExecutor` default always fails
|
|
90
|
+
/// when there is no active Swift task carrying this executor.
|
|
61
91
|
func checkIsolated() {
|
|
62
|
-
|
|
63
|
-
// thread name, not a precise isolation guarantee. Worklet runtimes legitimately run on
|
|
64
|
-
// the UI thread, which would cause a false-positive crash with `precondition`.
|
|
65
|
-
assert(isIsolatingCurrentContext() == true, "JavaScriptActor operations must be run on the JavaScript thread")
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/// Checks whether the executor isolates the current context, i.e. the current thread is a JavaScript thread.
|
|
69
|
-
/// The condition is also met when running tests and in single threaded environments.
|
|
70
|
-
func isIsolatingCurrentContext() -> Bool? {
|
|
71
|
-
// We must be careful as it relies on the thread name given by React Native.
|
|
72
|
-
return Thread.current.name == jsThreadName || !Thread.isMultiThreaded()
|
|
73
|
-
|| ProcessInfo.processInfo.processName == "xctest"
|
|
92
|
+
JavaScriptActor.checkIsolated()
|
|
74
93
|
}
|
|
75
94
|
}
|
|
76
95
|
|
|
@@ -50,7 +50,7 @@ public:
|
|
|
50
50
|
`scheduleTask` dispatches through `fn`, which the host implements against
|
|
51
51
|
the real react::RuntimeScheduler.
|
|
52
52
|
*/
|
|
53
|
-
RuntimeScheduler(void *scheduler, ScheduleFn fn) noexcept
|
|
53
|
+
SWIFT_RETURNS_RETAINED RuntimeScheduler(void *scheduler, ScheduleFn fn) noexcept
|
|
54
54
|
: nativeScheduler(scheduler), scheduleFn(fn) {}
|
|
55
55
|
|
|
56
56
|
/**
|
|
@@ -58,7 +58,7 @@ public:
|
|
|
58
58
|
caller's thread — intended for standalone runtimes (e.g. tests) that have
|
|
59
59
|
no React scheduler.
|
|
60
60
|
*/
|
|
61
|
-
RuntimeScheduler() {}
|
|
61
|
+
SWIFT_RETURNS_RETAINED RuntimeScheduler() {}
|
|
62
62
|
|
|
63
63
|
RuntimeScheduler(const RuntimeScheduler &) = delete;
|
|
64
64
|
|
|
@@ -58,6 +58,17 @@ struct JavaScriptActorTests {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
@Test
|
|
62
|
+
func `assumeIsolated preserves typed error`() {
|
|
63
|
+
struct CustomError: Error {}
|
|
64
|
+
|
|
65
|
+
#expect(throws: CustomError.self) {
|
|
66
|
+
try JavaScriptActor.assumeIsolated { () throws(CustomError) -> Int in
|
|
67
|
+
throw CustomError()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
61
72
|
@Test
|
|
62
73
|
func `assumeIsolated can modify captured variables`() {
|
|
63
74
|
var counter = 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-modules-jsi",
|
|
3
|
-
"version": "57.0.
|
|
3
|
+
"version": "57.0.6",
|
|
4
4
|
"description": "The JavaScript Interface for Expo Modules",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"sideEffects": [],
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"./apple/scripts/test.sh"
|
|
42
42
|
]
|
|
43
43
|
},
|
|
44
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "c300d2cc60c9e684e64f48d9bc90ea18a571d01d",
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "apple/scripts/build-xcframework.sh",
|
|
47
47
|
"clean": "apple/scripts/clear-caches.sh",
|