Skip to content

Memory leak: ~150MB growth when repeatedly creating/destroying RiveViewModel with enableAutoBind #427

Description

@mfazekas

Description

When repeatedly creating and destroying RiveViewModel + RiveView with enableAutoBind on a .riv file that has data binding, memory grows ~150MB over 10 cycles and never recovers.

Provide a Repro

Minimal SwiftUI reproduction — toggles a RiveViewModel with enableAutoBind on/off 10 times:

struct MemoryLeakTestView: View {
    @State private var showRive = false
    @State private var cycle = 0
    @State private var running = false

    var body: some View {
        VStack {
            Text("Cycles: \(cycle)")
            if showRive {
                RiveViewWrapper()
                    .frame(height: 200)
            }
            Button("Run 10 Cycles") {
                guard !running else { return }
                running = true
                cycle = 0
                runNextCycle()
            }
        }
    }

    private func runNextCycle() {
        guard cycle < 10 else {
            showRive = false
            running = false
            return
        }
        cycle += 1
        showRive = false
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            showRive = true
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                runNextCycle()
            }
        }
    }
}

private struct RiveViewWrapper: View {
    @StateObject private var vm: RiveViewModel = {
        let vm = RiveViewModel(fileName: "blinko", autoPlay: true)
        vm.riveModel?.enableAutoBind { _ in }
        return vm
    }()
    var body: some View { vm.view() }
}

Steps:

  1. Profile with Instruments → Allocations (Cmd+I)
  2. Tap "Run 10 Cycles"
  3. Observe memory grows ~150MB and never decreases

Source .riv/.rev file

Reproduced with blinko.riv (already in this repo).

Expected behavior

After RiveViewModel is deallocated and SwiftUI destroys the view, memory should return to baseline.

Screenshots

Image

Device & Versions (please complete the following information)

  • Device: iOS Simulator, iPhone 16
  • iOS version: iOS 18.2
  • RiveRuntime: 6.12.1 (SPM)
  • Xcode: 16.2

Additional context

  • Originally discovered in @rive-app/react-native, confirmed in the core rive-ios SDK
  • The leak is deterministic — same cycles always produce the same growth
  • Without enableAutoBind, the leak does not occur

See rive-app/rive-nitro-react-native#167

Activity

  1. mfazekas commented on Mar 4, 2026

    @mfazekas
    ContributorAuthor

    Memory graph analysis

    Analyzed the memgraph with leaks --trace. The rive::ArtboardInstance objects are not traditional leaks — they're still reachable. The retention path:

    CFRunLoopSource
      → rive::ViewModelInstanceNumber (delegate list)
        → ViewModelInstanceValue::addDelegate()
          → [non-object from RiveRuntime]
            → rive::ArtboardInstance   ← kept alive
    

    The ViewModelInstanceValue::addDelegate() creates a reference from the ViewModelInstanceNumber back into the ArtboardInstance's object tree. When enableAutoBind creates bindings, these delegates are added but never removed during teardown. Since the ViewModelInstanceNumber is itself still reachable (via a CFRunLoopSource on an IOHIDEventSystemClient dispatch queue), the entire ArtboardInstance and all its children stay alive.

    There's also a secondary retention path through Metal's debug layer:

    MTLDebugDevice → MTLToolsObjectCache._keyRemoveList
      → rive::TextStylePaint → rive::FontAsset
        → FileAssetReferencer::setAsset()
          → rive::TextStylePaint
            → rive::ArtboardInstance
    

    Heap stats from the memgraph (368MB physical footprint after 10 cycles):

    Count Bytes Type
    6,783 24.3 MB rive::ArtboardInstance
    29,701 45.6 MB rive::Ellipse
    29,254 26.2 MB rive::Shape
    95,780 13.8 MB rive::DataBindContext
    72,622 13.9 MB rive::SolidColor
    65,139 15.6 MB rive::Fill
    22,712 5.1 MB rive::AudioEvent
    6,783 4.2 MB rive::StateMachineInstance

    All of these are children owned by the leaked ArtboardInstances.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions