From 3adc017dab096892a916cc8a496f3e480b0f9a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Fri, 17 Apr 2026 12:28:15 +0200 Subject: [PATCH] fix(iOS): use per-call ReferencedAssetLoader to prevent concurrent access crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shared ReferencedAssetLoader on HybridRiveFileFactory caused a data race when multiple RiveFile loads ran concurrently. Concurrent GCD threads calling setFileRef() on the same instance wrote to activeFileRef without synchronization — a non-atomic property write that double-released the previous RiveFile, causing EXC_BAD_ACCESS crashes. --- ios/HybridRiveFileFactory.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ios/HybridRiveFileFactory.swift b/ios/HybridRiveFileFactory.swift index 154f1d02..dc685a4c 100644 --- a/ios/HybridRiveFileFactory.swift +++ b/ios/HybridRiveFileFactory.swift @@ -2,7 +2,6 @@ import NitroModules import RiveRuntime final class HybridRiveFileFactory: HybridRiveFileFactorySpec, @unchecked Sendable { - let assetLoader = ReferencedAssetLoader() /// Asynchronously creates a `HybridRiveFileSpec` by performing the following steps: /// 1. Executes `check()` to validate or fetch initial data. @@ -34,9 +33,10 @@ final class HybridRiveFileFactory: HybridRiveFileFactorySpec, @unchecked Sendabl DispatchQueue.global(qos: .userInitiated).async { do { + let assetLoader = ReferencedAssetLoader() let referencedAssetCache = SendableRef(ReferencedAssetCache()) let factoryCache: SendableRef = .init(nil) - let customLoader = self.assetLoader.createCustomLoader( + let customLoader = assetLoader.createCustomLoader( referencedAssets: referencedAssets, cache: referencedAssetCache, factory: factoryCache ) @@ -47,11 +47,13 @@ final class HybridRiveFileFactory: HybridRiveFileFactorySpec, @unchecked Sendabl } else { try file(prepared) } - self.assetLoader.setFileRef(riveFile) + if customLoader != nil { + assetLoader.setFileRef(riveFile) + } let result = ( file: riveFile, cache: referencedAssetCache.value, factory: factoryCache.value, - loader: customLoader != nil ? self.assetLoader : nil + loader: customLoader != nil ? assetLoader : nil ) DispatchQueue.main.async { continuation.resume(returning: result)