Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1357,7 +1357,7 @@ PODS:
- React-jsiexecutor
- React-RCTFBReactNativeSpec
- ReactCommon/turbomodule/core
- react-native-safe-area-context (5.6.2):
- react-native-safe-area-context (5.7.0):
- DoubleConversion
- glog
- hermes-engine
Expand All @@ -1372,8 +1372,8 @@ PODS:
- React-hermes
- React-ImageManager
- React-jsi
- react-native-safe-area-context/common (= 5.6.2)
- react-native-safe-area-context/fabric (= 5.6.2)
- react-native-safe-area-context/common (= 5.7.0)
- react-native-safe-area-context/fabric (= 5.7.0)
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
Expand All @@ -1383,7 +1383,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-safe-area-context/common (5.6.2):
- react-native-safe-area-context/common (5.7.0):
- DoubleConversion
- glog
- hermes-engine
Expand All @@ -1407,7 +1407,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-safe-area-context/fabric (5.6.2):
- react-native-safe-area-context/fabric (5.7.0):
- DoubleConversion
- glog
- hermes-engine
Expand Down Expand Up @@ -1754,7 +1754,7 @@ PODS:
- React-logger (= 0.79.2)
- React-perflogger (= 0.79.2)
- React-utils (= 0.79.2)
- RiveRuntime (6.18.2)
- RiveRuntime (6.20.4)
- RNCAsyncStorage (2.2.0):
- DoubleConversion
- glog
Expand Down Expand Up @@ -1904,7 +1904,7 @@ PODS:
- ReactCommon/turbomodule/core
- RNWorklets
- Yoga
- RNRive (0.4.2):
- RNRive (0.4.7):
- DoubleConversion
- glog
- hermes-engine
Expand All @@ -1928,7 +1928,7 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RiveRuntime (= 6.18.2)
- RiveRuntime (= 6.20.4)
- Yoga
- RNScreens (4.18.0):
- DoubleConversion
Expand Down Expand Up @@ -2347,7 +2347,7 @@ SPEC CHECKSUMS:
React-logger: 8edfcedc100544791cd82692ca5a574240a16219
React-Mapbuffer: c3f4b608e4a59dd2f6a416ef4d47a14400194468
React-microtasksnativemodule: 054f34e9b82f02bd40f09cebd4083828b5b2beb6
react-native-safe-area-context: 0b8555c40461feb7198e999912a3446602e7c601
react-native-safe-area-context: 26634d9b636a98ceee20cb6fa5dc946922f1e90f
React-NativeModulesApple: 2c4377e139522c3d73f5df582e4f051a838ff25e
React-oscompat: ef5df1c734f19b8003e149317d041b8ce1f7d29c
React-perflogger: 9a151e0b4c933c9205fd648c246506a83f31395d
Expand Down Expand Up @@ -2379,12 +2379,12 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: 04d5eb15eb46be6720e17a4a7fa92940a776e584
ReactCodegen: c63eda03ba1d94353fb97b031fc84f75a0d125ba
ReactCommon: 76d2dc87136d0a667678668b86f0fca0c16fdeb0
RiveRuntime: 55c7a7badd9a8389d20fc8a75b7c6accc851b69a
RiveRuntime: f99ddd1d4b6a420dea5943ff52502e8f92ae7a92
RNCAsyncStorage: a1c8cc8a99c32de1244a9cf707bf9d83d0de0f71
RNCPicker: 28c076ae12a1056269ec0305fe35fac3086c477d
RNGestureHandler: 6b39f4e43e4b3a0fb86de9531d090ff205a011d5
RNReanimated: 66b68ebe3baf7ec9e716bd059d700726f250d344
RNRive: c02b3545abcf477d074945c5103f9f4bc9d8d672
RNRive: 042cbd7de1e5dd7e11aac076ade185926717543b
RNScreens: f38464ec1e83bda5820c3b05ccf4908e3841c5cc
RNWorklets: b1faafefb82d9f29c4018404a0fb33974b494a7b
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Expand Down
81 changes: 50 additions & 31 deletions ios/ReferencedAssetLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ func createAssetFileError(_ assetName: String) -> NitroRiveError {
}

final class ReferencedAssetLoader {
private static let decodeQueue = DispatchQueue(label: "com.rive.asset-decode")
private var activeLoadCount = 0
private var activeFileRef: RiveFile?

Expand All @@ -35,46 +36,64 @@ final class ReferencedAssetLoader {
RCTLogError("\(error)")
}

private func processAssetBytes(
_ data: Data, asset: RiveFileAsset, factory: RiveFactory, completion: @escaping () -> Void
/// Decodes an asset and applies the result on the main thread.
///
/// - `onMain: true` — decode + apply run synchronously on the caller's
/// thread (must already be main). Used for `decodeImage` which is not
/// thread-safe.
/// - `onMain: false` — decode runs on a serial background queue, then
/// apply + completion dispatch to main. The `[self]` capture keeps the
/// `ReferencedAssetLoader` (and its `activeFileRef`) alive until the
/// main-thread block completes, preventing use-after-free on the factory.
private func decodeAndApply<T>(
onMain: Bool,
decode: @escaping () -> T?,
apply: @escaping (T) -> Void,
completion: @escaping () -> Void
) {
if data.isEmpty == true {
if onMain {
if let result = decode() { apply(result) }
completion()
return
}
DispatchQueue.global(qos: .background).async {
switch asset {
case let imageAsset as RiveImageAsset:
let decodedImage = factory.decodeImage(data)
DispatchQueue.main.async {
imageAsset.renderImage(decodedImage)
completion()
}
case let fontAsset as RiveFontAsset:
let decodedFont = factory.decodeFont(data)
DispatchQueue.main.async {
fontAsset.font(decodedFont)
completion()
}
case let audioAsset as RiveAudioAsset:
guard let decodedAudio = factory.decodeAudio(data) else {
DispatchQueue.main.async {
completion()
}
return
}
DispatchQueue.main.async {
audioAsset.audio(decodedAudio)
completion()
}
default:
} else {
Self.decodeQueue.async { [self] in
let result = decode()
DispatchQueue.main.async {
if let result { apply(result) }
completion()
_ = self
}
}
}
}

private func processAssetBytes(
_ data: Data, asset: RiveFileAsset, factory: RiveFactory, completion: @escaping () -> Void
) {
if data.isEmpty {
completion()
return
}
switch asset {
case let imageAsset as RiveImageAsset:
decodeAndApply(onMain: true,
decode: { factory.decodeImage(data) },
apply: { imageAsset.renderImage($0) },
completion: completion)
case let fontAsset as RiveFontAsset:
decodeAndApply(onMain: false,
decode: { factory.decodeFont(data) },
apply: { fontAsset.font($0) },
completion: completion)
case let audioAsset as RiveAudioAsset:
decodeAndApply(onMain: false,
decode: { factory.decodeAudio(data) },
apply: { audioAsset.audio($0) },
completion: completion)
default:
completion()
}
}

private func handlePreloadedImage(
_ image: any HybridRiveImageSpec, asset: RiveFileAsset, completion: @escaping () -> Void
) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
},
"homepage": "https://github.com/rive-app/rive-nitro-react-native#readme",
"runtimeVersions": {
"ios": "6.18.2",
"ios": "6.20.4",
"android": "11.4.0"
},
"publishConfig": {
Expand Down
Loading