Skip to content

Commit da30534

Browse files
committed
refactor(ios): extract decodeAndApply helper for asset decode logic
1 parent f0661dd commit da30534

1 file changed

Lines changed: 43 additions & 25 deletions

File tree

ios/ReferencedAssetLoader.swift

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -36,41 +36,59 @@ final class ReferencedAssetLoader {
3636
RCTLogError("\(error)")
3737
}
3838

39+
/// Decodes an asset and applies the result on the main thread.
40+
///
41+
/// - `onMain: true` — decode + apply run synchronously on the caller's
42+
/// thread (must already be main). Used for `decodeImage` which is not
43+
/// thread-safe.
44+
/// - `onMain: false` — decode runs on a serial background queue, then
45+
/// apply + completion dispatch to main. The `[self]` capture keeps the
46+
/// `ReferencedAssetLoader` (and its `activeFileRef`) alive until the
47+
/// main-thread block completes, preventing use-after-free on the factory.
48+
private func decodeAndApply<T>(
49+
onMain: Bool,
50+
decode: @escaping () -> T?,
51+
apply: @escaping (T) -> Void,
52+
completion: @escaping () -> Void
53+
) {
54+
if onMain {
55+
if let result = decode() { apply(result) }
56+
completion()
57+
} else {
58+
Self.decodeQueue.async { [self] in
59+
let result = decode()
60+
DispatchQueue.main.async {
61+
if let result { apply(result) }
62+
completion()
63+
_ = self
64+
}
65+
}
66+
}
67+
}
68+
3969
private func processAssetBytes(
4070
_ data: Data, asset: RiveFileAsset, factory: RiveFactory, completion: @escaping () -> Void
4171
) {
42-
if data.isEmpty == true {
72+
if data.isEmpty {
4373
completion()
4474
return
4575
}
4676
switch asset {
4777
case let imageAsset as RiveImageAsset:
48-
// decodeImage is not thread-safe — decode on main thread, synchronously
49-
// within the current MainActor context (caller is already on main).
50-
let decodedImage = factory.decodeImage(data)
51-
imageAsset.renderImage(decodedImage)
52-
completion()
78+
decodeAndApply(onMain: true,
79+
decode: { factory.decodeImage(data) },
80+
apply: { imageAsset.renderImage($0) },
81+
completion: completion)
5382
case let fontAsset as RiveFontAsset:
54-
Self.decodeQueue.async { [self] in
55-
let decodedFont = factory.decodeFont(data)
56-
DispatchQueue.main.async {
57-
fontAsset.font(decodedFont)
58-
completion()
59-
_ = self
60-
}
61-
}
83+
decodeAndApply(onMain: false,
84+
decode: { factory.decodeFont(data) },
85+
apply: { fontAsset.font($0) },
86+
completion: completion)
6287
case let audioAsset as RiveAudioAsset:
63-
Self.decodeQueue.async { [self] in
64-
guard let decodedAudio = factory.decodeAudio(data) else {
65-
DispatchQueue.main.async { completion() }
66-
return
67-
}
68-
DispatchQueue.main.async {
69-
audioAsset.audio(decodedAudio)
70-
completion()
71-
_ = self
72-
}
73-
}
88+
decodeAndApply(onMain: false,
89+
decode: { factory.decodeAudio(data) },
90+
apply: { audioAsset.audio($0) },
91+
completion: completion)
7492
default:
7593
completion()
7694
}

0 commit comments

Comments
 (0)