expo-modules-core 55.0.17 → 55.0.18
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 +10 -2
- package/android/build.gradle +2 -2
- package/ios/Core/ExpoBridgeModule.mm +6 -2
- package/ios/Core/Logging/PersistentFileLog.swift +5 -3
- package/ios/Core/ModuleRegistry.swift +39 -14
- package/ios/Core/SharedObjects/SharedObjectRegistry.swift +12 -12
- package/ios/Core/Views/SwiftUI/SwiftUIHostingView.swift +3 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,16 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 55.0.18 — 2026-03-27
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug fixes
|
|
16
|
+
|
|
17
|
+
- [iOS] Fixed adding SwiftUI views to tabs BottomAccessory. ([#44247](https://github.com/expo/expo/pull/44247) by [@t0maboro](https://github.com/t0maboro))
|
|
18
|
+
- [iOS] Fix crash that can occur when reloading JavaScript bundle due to object deallocating off the JavaScript thread. ([#43937](https://github.com/expo/expo/pull/43937) by [@cltnschlosser](https://github.com/cltnschlosser))
|
|
19
|
+
- [iOS] Fixed runtime setup crashing in the old bridge module. ([#44121](https://github.com/expo/expo/pull/44121) by [@tsapeta](https://github.com/tsapeta))
|
|
20
|
+
- [iOS] Make access to module registry thread safe. ([#44042](https://github.com/expo/expo/pull/44042) by [@alanjhughes](https://github.com/alanjhughes))
|
|
21
|
+
- [iOS] Fixed `PersistentFileLog.readEntries` race condition by serializing reads on the dispatch queue. ([#43958](https://github.com/expo/expo/pull/43958) by [@ramonclaudio](https://github.com/ramonclaudio))
|
|
22
|
+
|
|
13
23
|
## 55.0.17 — 2026-03-18
|
|
14
24
|
|
|
15
25
|
_This version does not introduce any user-facing changes._
|
|
@@ -24,8 +34,6 @@ _This version does not introduce any user-facing changes._
|
|
|
24
34
|
|
|
25
35
|
- [iOS] Fixed compilation issues due to missing fallthrough case in `EXJavaScriptSerializable`. ([#43634](https://github.com/expo/expo/pull/43634) by [@tjzel](https://github.com/tjzel))
|
|
26
36
|
|
|
27
|
-
## 55.0.15 — 2026-03-11
|
|
28
|
-
|
|
29
37
|
### 💡 Others
|
|
30
38
|
|
|
31
39
|
- [iOS] Add `ViewWrapper` protocol and `AnyContentViewProvider` for type-erased access to wrapped SwiftUI views. ([#43669](https://github.com/expo/expo/pull/43669) by [@nishan](https://github.com/intergalacticspacehighway))
|
package/android/build.gradle
CHANGED
|
@@ -29,7 +29,7 @@ if (shouldIncludeCompose) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
group = 'host.exp.exponent'
|
|
32
|
-
version = '55.0.
|
|
32
|
+
version = '55.0.18'
|
|
33
33
|
|
|
34
34
|
def isExpoModulesCoreTests = {
|
|
35
35
|
Gradle gradle = getGradle()
|
|
@@ -96,7 +96,7 @@ android {
|
|
|
96
96
|
defaultConfig {
|
|
97
97
|
consumerProguardFiles 'proguard-rules.pro'
|
|
98
98
|
versionCode 1
|
|
99
|
-
versionName "55.0.
|
|
99
|
+
versionName "55.0.18"
|
|
100
100
|
buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\""
|
|
101
101
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true"
|
|
102
102
|
|
|
@@ -36,7 +36,6 @@ RCT_EXPORT_MODULE(ExpoModulesCore);
|
|
|
36
36
|
- (void)setBridge:(RCTBridge *)bridge
|
|
37
37
|
{
|
|
38
38
|
_bridge = bridge;
|
|
39
|
-
[self maybeSetupAppContext];
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
- (void)maybeSetupAppContext
|
|
@@ -46,10 +45,15 @@ RCT_EXPORT_MODULE(ExpoModulesCore);
|
|
|
46
45
|
}
|
|
47
46
|
EXRuntime *runtime = [EXJavaScriptRuntimeManager runtimeFromBridge:_bridge];
|
|
48
47
|
|
|
48
|
+
if (!runtime) {
|
|
49
|
+
NSLog(@"Unable to get the JSI runtime from the bridge instance, make sure to use ExpoReactNativeFactory for newer bridgeless integration");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
49
53
|
// If `global.expo` is defined, the app context has already been initialized from `ExpoReactNativeFactory`.
|
|
50
54
|
// The factory was introduced in SDK 55 and requires migration in bare workflow projects.
|
|
51
55
|
// We keep this as an alternative way during the transitional period.
|
|
52
|
-
if (
|
|
56
|
+
if (![[runtime global] hasProperty:@"expo"]) {
|
|
53
57
|
NSLog(@"Expo is being initialized from the deprecated ExpoBridgeModule, make sure to migrate to ExpoReactNativeFactory in your project");
|
|
54
58
|
|
|
55
59
|
_appContext.reactBridge = _bridge;
|
|
@@ -43,10 +43,12 @@ public class PersistentFileLog {
|
|
|
43
43
|
Read entries from log file
|
|
44
44
|
*/
|
|
45
45
|
public func readEntries() -> [String] {
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
return PersistentFileLog.serialQueue.sync {
|
|
47
|
+
if getFileSize() == 0 {
|
|
48
|
+
return []
|
|
49
|
+
}
|
|
50
|
+
return (try? self.readFileSync()) ?? []
|
|
48
51
|
}
|
|
49
|
-
return (try? self.readFileSync()) ?? []
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
/**
|
|
@@ -5,6 +5,7 @@ public final class ModuleRegistry: Sequence {
|
|
|
5
5
|
|
|
6
6
|
private var registry: [String: ModuleHolder] = [:]
|
|
7
7
|
private var overrideDisallowModules = Set<String>()
|
|
8
|
+
private let lock = NSLock()
|
|
8
9
|
|
|
9
10
|
init(appContext: AppContext) {
|
|
10
11
|
self.appContext = appContext
|
|
@@ -16,6 +17,8 @@ public final class ModuleRegistry: Sequence {
|
|
|
16
17
|
internal func register(holder: ModuleHolder, preventModuleOverriding: Bool = false) {
|
|
17
18
|
log.info("Registering module '\(holder.name)'")
|
|
18
19
|
|
|
20
|
+
lock.lock()
|
|
21
|
+
defer { lock.unlock() }
|
|
19
22
|
// if overriding is disallowed for this module and the module already registered, don't re-register
|
|
20
23
|
if overrideDisallowModules.contains(holder.name) && registry[holder.name] != nil {
|
|
21
24
|
log.info("Not re-registering module '\(holder.name)' since a previous registration specified preventModuleOverriding: true")
|
|
@@ -63,45 +66,61 @@ public final class ModuleRegistry: Sequence {
|
|
|
63
66
|
Unregisters given module from the registry.
|
|
64
67
|
*/
|
|
65
68
|
public func unregister(module: AnyModule) {
|
|
66
|
-
|
|
67
|
-
registry.
|
|
69
|
+
withLock {
|
|
70
|
+
if let index = registry.firstIndex(where: { $1.module === module }) {
|
|
71
|
+
registry.remove(at: index)
|
|
72
|
+
}
|
|
68
73
|
}
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
public func unregister(moduleName: String) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
withLock {
|
|
78
|
+
if registry[moduleName] != nil {
|
|
79
|
+
log.info("Unregistering module '\(moduleName)'")
|
|
80
|
+
registry[moduleName] = nil
|
|
81
|
+
}
|
|
75
82
|
}
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
public func has(moduleWithName moduleName: String) -> Bool {
|
|
79
|
-
return
|
|
86
|
+
return withLock {
|
|
87
|
+
return registry[moduleName] != nil
|
|
88
|
+
}
|
|
80
89
|
}
|
|
81
90
|
|
|
82
91
|
public func get(moduleHolderForName moduleName: String) -> ModuleHolder? {
|
|
83
|
-
return
|
|
92
|
+
return withLock {
|
|
93
|
+
return registry[moduleName]
|
|
94
|
+
}
|
|
84
95
|
}
|
|
85
96
|
|
|
86
97
|
public func get(moduleWithName moduleName: String) -> AnyModule? {
|
|
87
|
-
return
|
|
98
|
+
return withLock {
|
|
99
|
+
registry[moduleName]?.module
|
|
100
|
+
}
|
|
88
101
|
}
|
|
89
102
|
|
|
90
103
|
internal func getModule<ModuleProtocol>(implementing protocol: ModuleProtocol.Type) -> ModuleProtocol? {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
104
|
+
return withLock {
|
|
105
|
+
for holder in registry.values {
|
|
106
|
+
if let module = holder.module as? ModuleProtocol {
|
|
107
|
+
return module
|
|
108
|
+
}
|
|
94
109
|
}
|
|
110
|
+
return nil
|
|
95
111
|
}
|
|
96
|
-
return nil
|
|
97
112
|
}
|
|
98
113
|
|
|
99
114
|
public func getModuleNames() -> [String] {
|
|
100
|
-
return
|
|
115
|
+
return withLock {
|
|
116
|
+
return Array(registry.keys)
|
|
117
|
+
}
|
|
101
118
|
}
|
|
102
119
|
|
|
103
120
|
public func makeIterator() -> IndexingIterator<[ModuleHolder]> {
|
|
104
|
-
return
|
|
121
|
+
return withLock {
|
|
122
|
+
return registry.map({ $1 }).makeIterator()
|
|
123
|
+
}
|
|
105
124
|
}
|
|
106
125
|
|
|
107
126
|
internal func post(event: EventName) {
|
|
@@ -117,4 +136,10 @@ public final class ModuleRegistry: Sequence {
|
|
|
117
136
|
holder.post(event: event, payload: payload)
|
|
118
137
|
}
|
|
119
138
|
}
|
|
139
|
+
|
|
140
|
+
private func withLock<T>(block: () -> T) -> T {
|
|
141
|
+
lock.lock()
|
|
142
|
+
defer { lock.unlock() }
|
|
143
|
+
return block()
|
|
144
|
+
}
|
|
120
145
|
}
|
|
@@ -29,23 +29,23 @@ public final class SharedObjectRegistry {
|
|
|
29
29
|
The counter of IDs to assign to the shared object pairs.
|
|
30
30
|
The next pair added to the registry will be saved using this ID.
|
|
31
31
|
*/
|
|
32
|
-
internal var nextId: SharedObjectId = 1
|
|
32
|
+
internal /* visible for testing */ var nextId: SharedObjectId = 1
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
A dictionary of shared object pairs.
|
|
36
36
|
*/
|
|
37
|
-
|
|
37
|
+
private var pairs = [SharedObjectId: SharedObjectPair]()
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
The lock
|
|
40
|
+
The lock to keep thread safety for internal data structures.
|
|
41
41
|
*/
|
|
42
|
-
private
|
|
42
|
+
private let lock = NSLock()
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
A number of all pairs stored in the registry.
|
|
46
46
|
*/
|
|
47
47
|
internal var size: Int {
|
|
48
|
-
return
|
|
48
|
+
return lock.withLock {
|
|
49
49
|
return pairs.count
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -69,7 +69,7 @@ public final class SharedObjectRegistry {
|
|
|
69
69
|
*/
|
|
70
70
|
@discardableResult
|
|
71
71
|
internal func pullNextId() -> SharedObjectId {
|
|
72
|
-
return
|
|
72
|
+
return lock.withLock {
|
|
73
73
|
let id = nextId
|
|
74
74
|
nextId += 1
|
|
75
75
|
return id
|
|
@@ -80,7 +80,7 @@ public final class SharedObjectRegistry {
|
|
|
80
80
|
Returns a pair of shared objects with given ID or `nil` when there is no such pair in the registry.
|
|
81
81
|
*/
|
|
82
82
|
internal func get(_ id: SharedObjectId) -> SharedObjectPair? {
|
|
83
|
-
return
|
|
83
|
+
return lock.withLock {
|
|
84
84
|
return pairs[id]
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -117,7 +117,7 @@ public final class SharedObjectRegistry {
|
|
|
117
117
|
|
|
118
118
|
// Save the pair in the dictionary.
|
|
119
119
|
let jsWeakObject = jsObject.createWeak()
|
|
120
|
-
|
|
120
|
+
lock.withLock {
|
|
121
121
|
self.pairs[id] = (native: nativeObject, javaScript: jsWeakObject)
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -128,7 +128,7 @@ public final class SharedObjectRegistry {
|
|
|
128
128
|
Deletes the shared objects pair with a given ID.
|
|
129
129
|
*/
|
|
130
130
|
internal func delete(_ id: SharedObjectId) {
|
|
131
|
-
|
|
131
|
+
lock.withLock {
|
|
132
132
|
if let pair = self.pairs[id] {
|
|
133
133
|
pair.native.sharedObjectWillRelease()
|
|
134
134
|
// Reset an ID on the objects.
|
|
@@ -146,7 +146,7 @@ public final class SharedObjectRegistry {
|
|
|
146
146
|
*/
|
|
147
147
|
internal func toNativeObject(_ jsObject: JavaScriptObject) -> SharedObject? {
|
|
148
148
|
if let objectId = try? jsObject.getProperty(sharedObjectIdPropertyName).asInt() {
|
|
149
|
-
return
|
|
149
|
+
return lock.withLock {
|
|
150
150
|
return pairs[objectId]?.native
|
|
151
151
|
}
|
|
152
152
|
}
|
|
@@ -158,7 +158,7 @@ public final class SharedObjectRegistry {
|
|
|
158
158
|
*/
|
|
159
159
|
internal func toJavaScriptObject(_ nativeObject: SharedObject) -> JavaScriptObject? {
|
|
160
160
|
let objectId = nativeObject.sharedObjectId
|
|
161
|
-
return
|
|
161
|
+
return lock.withLock {
|
|
162
162
|
return pairs[objectId]?.javaScript.lock()
|
|
163
163
|
}
|
|
164
164
|
}
|
|
@@ -184,7 +184,7 @@ public final class SharedObjectRegistry {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
internal func clear() {
|
|
187
|
-
|
|
187
|
+
lock.withLock {
|
|
188
188
|
self.pairs.removeAll()
|
|
189
189
|
}
|
|
190
190
|
}
|
|
@@ -224,9 +224,10 @@ extension ExpoSwiftUI {
|
|
|
224
224
|
|
|
225
225
|
if window != nil, let parentController = reactViewController() {
|
|
226
226
|
#if !os(macOS)
|
|
227
|
-
if parentController as? UINavigationController == nil {
|
|
227
|
+
if parentController as? UINavigationController == nil && parentController as? UITabBarController == nil {
|
|
228
228
|
// Swift automatically adds the hostingController in the correct place when the parentController
|
|
229
|
-
// is UINavigationController, since it's children are supposed to be only screens
|
|
229
|
+
// is UINavigationController, since it's children are supposed to be only screens.
|
|
230
|
+
// Similarly, for UITabBarController we expect its children to be only tabs.
|
|
230
231
|
parentController.addChild(hostingController)
|
|
231
232
|
}
|
|
232
233
|
#else
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-modules-core",
|
|
3
|
-
"version": "55.0.
|
|
3
|
+
"version": "55.0.18",
|
|
4
4
|
"description": "The core of Expo Modules architecture",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -66,5 +66,5 @@
|
|
|
66
66
|
"@testing-library/react-native": "^13.3.0",
|
|
67
67
|
"expo-module-scripts": "^55.0.2"
|
|
68
68
|
},
|
|
69
|
-
"gitHead": "
|
|
69
|
+
"gitHead": "f63217deac00dcd278f75d9846933c11e5c6f9a3"
|
|
70
70
|
}
|