This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
mobilecritical PriorityID: kmp-stateflow-swift-interop-lifetime-anchor-standard

Deterministic Lifetime Anchoring for KMP Coroutine StateFlow Swift Interop

Enforces deterministic cancellation and deallocation bridging between Kotlin Coroutine Scopes and Swift Automatic Reference Counting (ARC) across the Objective-C/Swift interop barrier. Unanchored Kotlin StateFlow collectors bridge as unmanaged or global callbacks that keep underlying coroutine jobs alive indefinitely in native heaps, leaking memory, CPU cycles, and battery. Native Swift wrappers must own an explicit disposable lifetime anchor tied to deinit and native SwiftUI/UIKit view lifecycle lifespans.

Prohibited Anti-Pattern: Exposing Unmanaged Kotlin Coroutine Job Callbacks to Swift Retain Cycles Causing Zombie Background Execution

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: Deterministic Lifetime Anchoring for KMP Coroutine StateFlow Swift Interop - Enforces deterministic cancellation and deallocation bridging between Kotlin Coroutine Scopes and Swift Automatic Reference Counting (ARC) across the Objective-C/Swift interop barrier. Unanchored Kotlin StateFlow collectors bridge as unmanaged or global callbacks that keep underlying coroutine jobs alive indefinitely in native heaps, leaking memory, CPU cycles, and battery. Native Swift wrappers must own an explicit disposable lifetime anchor tied to deinit and native SwiftUI/UIKit view lifecycle lifespans.
globs: **/iosApp/**/*.swift, **/shared/src/iosMain/**/*.kt, **/shared/src/commonMain/**/*.kt, **/*ViewModel*.swift, **/*Adapter*.kt
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/kmp-stateflow-swift-interop-lifetime-anchor-standard"
---

# Deterministic Lifetime Anchoring for KMP Coroutine StateFlow Swift Interop

> **Severity**: CRITICAL | **Category**: mobile
> **Anti-Pattern Warning**: Exposing Unmanaged Kotlin Coroutine Job Callbacks to Swift Retain Cycles Causing Zombie Background Execution

## System Constraints
You are a Principal Mobile Architect enforcing strict memory and concurrency guarantees across the Kotlin Multiplatform (KMP) and Swift/Objective-C interop boundary. Mandate deterministic Coroutine Job cancellation anchored to Swift ARC deallocation. Prevent dormant coroutine leaks caused by unbound StateFlow/SharedFlow collection on Apple platforms.

## Strict Guidelines
- Engineers MUST ALWAYS expose a Kotlin-side `Closeable` or `DisposableHandle` wrapper from every exposed Flow/StateFlow observation function targeted at Swift.
- Engineers MUST NOT directly collect un-scoped KMP `StateFlow` or `SharedFlow` instances inside Swift without storing an unbind/cancellation token tied to the native owner's `deinit` or `onDisappear` lifecycle.
- Engineers MUST wrap all native Swift observation mechanisms (`ObservableObject`, `@Observable`, or custom SwiftUI bindings) in an adapter that cancels the underlying Kotlin `Job` inside `deinit`.
- Native Swift closures passed to Kotlin `StateFlow.collect` MUST capture external references weakly (`[weak self]`) to prevent strong reference cycles bridging ARC and Kotlin Native garbage collection.
- Kotlin-side multiplatform ViewModels MUST provide an explicit `clear()` or `onCleared()` entry point and MUST cancel all active `SupervisorJob` children when invoked from Swift lifecycles.

## Bad vs Good Implementations
### Swift Interop Flow Subscription Anchor

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Kotlin's StateFlow collect callback creates an untracked background coroutine subscription. When the native Swift view and ProfileViewModelWrapper dereference, ARC cannot clean up or notify the Kotlin runtime. The underlying coroutine scope keeps emitting across the boundary, causing dormant background execution and memory leaks.
```swift
import SwiftUI
import SharedKit

final class ProfileViewModelWrapper: ObservableObject {
    @Published var uiState: UserProfileState = .initial
    private let kotlinViewModel = ProfileViewModel()

    init() {
        // ANTI-PATTERN: Kotlin collect emits via callback but returns no disposable handle.
        // Swift closure captures strong self or runs on a Kotlin GlobalScope/detached CoroutineScope.
        // When this SwiftUI ObservableObject is destroyed, the Kotlin coroutine continues executing indefinitely.
        kotlinViewModel.state.collect(onEach: { [self] state in
            self.uiState = state
        })
    }
}
```

#### Production Standard (Correct)
> **Standard**: The Kotlin side returns a `KotlinInteropDisposableHandle` which wraps the native coroutine `Job`. Swift manages this handle through ARC, and on `deinit`, deterministically calls `dispose()`, cancelling the coroutine on the Kotlin Native thread and breaking cross-runtime object retention.
```swift
import SwiftUI
import SharedKit

@MainActor
final class ProfileViewModelWrapper: ObservableObject {
    @Published private(set) var uiState: UserProfileState = .initial
    private let kotlinViewModel: ProfileViewModel
    private var cancellableAnchor: KotlinInteropDisposableHandle?

    init(kotlinViewModel: ProfileViewModel = ProfileViewModel()) {
        self.kotlinViewModel = kotlinViewModel
        self.bindToKotlinFlow()
    }

    private func bindToKotlinFlow() {
        // PRODUCTION FIX: Explicit lifetime anchor returned across the interop barrier.
        // Weak capture prevents native retain cycles; cancel is deterministically tied to ARC deinit.
        self.cancellableAnchor = kotlinViewModel.bindStateFlow(
            onEmit: { [weak self] state in
                self?.uiState = state
            }
        )
    }

    deinit {
        cancellableAnchor?.dispose()
        kotlinViewModel.clear()
    }
}
```

### Kotlin Side Flow Binding Anchor Interface

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Exposing a blind launch without returning a cancellation handle prevents Swift clients from deterministic resource management, forcing reliance on GC or leaving coroutines orphan-running in background worker threads.
```kotlin
// ANTI-PATTERN: Direct exposure of coroutine collector to Objective-C/Swift without lifetime handle
package com.example.shared

import kotlinx.coroutines.flow.StateFlow

class ProfileViewModel {
    val state: StateFlow<UserProfileState> = ...

    // Exposes raw collector that Swift cannot deterministically cancel without full Job machinery
    fun collect(onEach: (UserProfileState) -> Unit) {
        // Hidden or uncontrolled coroutine scope launch
        coroutineScope.launch {
            state.collect { onEach(it) }
        }
    }
}
```

#### Production Standard (Correct)
> **Standard**: Kotlin creates a distinct child `Job` for the Swift collector and wraps its cancellation in `KotlinInteropDisposableHandle`. When Swift deallocates its wrapper, `dispose()` immediately cancels the Kotlin job, releasing references in both Kotlin GC and Swift ARC domains.
```kotlin
// PRODUCTION FIX: Concrete Swift-friendly disposable anchor
package com.example.shared

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.StateFlow

fun interface KotlinInteropDisposableHandle {
    fun dispose()
}

class ProfileViewModel {
    private val viewModelScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
    val state: StateFlow<UserProfileState> = ...

    fun bindStateFlow(onEmit: (UserProfileState) -> Unit): KotlinInteropDisposableHandle {
        val job = viewModelScope.launch {
            state.collect { onEmit(it) }
        }
        return KotlinInteropDisposableHandle {
            job.cancel()
        }
    }

    fun clear() {
        viewModelScope.cancel()
    }
}
```

Recommended MCP Servers (mcp-serverlist.com)

Equip agent with system tools to verify code for this stack.

Recommended Agent Skills (askills.dev)

Ready-to-use executable skill functions tailored to this stack.

Architectural Specification & Invariant Rationale

Architectural Invariant Design: Deterministic Lifetime Anchoring for KMP Coroutine StateFlow Swift Interop

Deterministic Lifetime Anchoring for KMP Coroutine StateFlow Swift Interop defines an industrial-grade behavioral standard for mobile engineering. Enforces deterministic cancellation and deallocation bridging between Kotlin Coroutine Scopes and Swift Automatic Reference Counting (ARC) across the Objective-C/Swift interop barrier. Unanchored Kotlin StateFlow collectors bridge as unmanaged or global callbacks that keep underlying coroutine jobs alive indefinitely in native heaps, leaking memory, CPU cycles, and battery. Native Swift wrappers must own an explicit disposable lifetime anchor tied to deinit and native SwiftUI/UIKit view lifecycle lifespans. Modern software architectures require strict adherence to deterministic patterns to guarantee high availability, maintainability, and zero security vulnerabilities.

Framework Paradigm Shift & Drift MechanicsLLMs are trained on millions of open-source repositories containing outdated code, legacy idioms, and unpatched antipatterns. When coding assistants generate implementations for this stack, they default to common statistical tokens: Exposing Unmanaged Kotlin Coroutine Job Callbacks to Swift Retain Cycles Causing Zombie Background Execution Without an explicit system instruction file, developers spend hours debugging silent failures.
Failure Modes & Production Blast Radius

Hazard Analysis: What Breaks Without This Rule

The following failure modes have been cataloged from hundreds of hours of production code reviews where AI assistants produced non-compliant implementations:

Failure ModeAI Symptom (The Anti-Pattern)SeverityProduction Impact & Remedy
AI Hallucination & Deprecation DriftExposing Unmanaged Kotlin Coroutine Job Callbacks to Swift Retain Cycles Causing Zombie Background ExecutionCritical
Violating this industrial standard introduces regression bugs, security risks, and unexpected runtime failures in production.
Enforce explicit behavioral system directives across all AI coding assistants in your repository.
Silent Architecture InconsistencyAI assistants produce mixed paradigms across files, creating fragmented codebase conventions.Medium
Increases cognitive load during code reviews and leads to maintenance debt across engineering teams.
Pin project conventions in .cursor/rules, CLAUDE.md, and AGENTS.md so all team agents follow identical guardrails.
Architectural Directives in Detail

Step-by-Step Production Directives

Each rule constraint below is injected into the AI agent's system prompt to establish deterministic boundaries:

1

Engineers MUST ALWAYS expose a Kotlin-side `Closeable` or `DisposableHandle` wrapper from every exposed Flow/StateFlow observation function targeted at Swift.

Engineers MUST ALWAYS expose a Kotlin-side `Closeable` or `DisposableHandle` wrapper from every exposed Flow/StateFlow observation function targeted at Swift.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
2

Engineers MUST NOT directly collect un-scoped KMP `StateFlow` or `SharedFlow` instances inside Swift without storing an unbind/cancellation token tied to the native owner's `deinit` or `onDisappear` lifecycle.

Engineers MUST NOT directly collect un-scoped KMP `StateFlow` or `SharedFlow` instances inside Swift without storing an unbind/cancellation token tied to the native owner's `deinit` or `onDisappear` lifecycle.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
3

Engineers MUST wrap all native Swift observation mechanisms (`ObservableObject`, `@Observable`, or custom SwiftUI bindings) in an adapter that cancels the underlying Kotlin `Job` inside `deinit`.

Engineers MUST wrap all native Swift observation mechanisms (`ObservableObject`, `@Observable`, or custom SwiftUI bindings) in an adapter that cancels the underlying Kotlin `Job` inside `deinit`.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
4

Native Swift closures passed to Kotlin `StateFlow.collect` MUST capture external references weakly (`[weak self]`) to prevent strong reference cycles bridging ARC and Kotlin Native garbage collection.

Native Swift closures passed to Kotlin `StateFlow.collect` MUST capture external references weakly (`[weak self]`) to prevent strong reference cycles bridging ARC and Kotlin Native garbage collection.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
5

Kotlin-side multiplatform ViewModels MUST provide an explicit `clear()` or `onCleared()` entry point and MUST cancel all active `SupervisorJob` children when invoked from Swift lifecycles.

Kotlin-side multiplatform ViewModels MUST provide an explicit `clear()` or `onCleared()` entry point and MUST cancel all active `SupervisorJob` children when invoked from Swift lifecycles.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
Technical Questions & Answers

Frequently Asked Technical Questions

Common questions developers face when configuring AI coding assistants for this stack:

Q:Why is Deterministic Lifetime Anchoring for KMP Coroutine StateFlow Swift Interop critical for AI coding assistants?

Enforces deterministic cancellation and deallocation bridging between Kotlin Coroutine Scopes and Swift Automatic Reference Counting (ARC) across the Objective-C/Swift interop barrier. Unanchored Kotlin StateFlow collectors bridge as unmanaged or global callbacks that keep underlying coroutine jobs alive indefinitely in native heaps, leaking memory, CPU cycles, and battery. Native Swift wrappers must own an explicit disposable lifetime anchor tied to deinit and native SwiftUI/UIKit view lifecycle lifespans. Without explicit behavioral boundaries, AI coding assistants (Cursor, Claude Code, Windsurf, Codex) rely on training data averages, which frequently produce deprecated, inefficient, or vulnerable code patterns.

Q:How does adopting this rule prevent AI hallucinations?

This rule explicitly declares what the AI must avoid (Exposing Unmanaged Kotlin Coroutine Job Callbacks to Swift Retain Cycles Causing Zombie Background Execution) and prescribes verified positive examples. Grounding the LLM with negative and positive pairs eliminates guesswork.

Q:Which AI coding environments are supported?

This specification is multi-platform compatible: Cursor (.cursor/rules/*.mdc & .cursorrules), Anthropic Claude Code (CLAUDE.md), Windsurf (.windsurfrules), Codex / Cline (AGENTS.md), and GitHub Copilot (.github/copilot-instructions.md).

Q:Can I combine this rule with other technology stacks?

Yes. Use the Interactive Stack Builder or download an Agent Pack to merge this rule with other frontend, backend, database, security, and DevOps rules without syntax collision.

Open Specification · CC BY 4.0·Free for Personal, Team & Commercial In-Repo Use

Citation & Open Source Attribution Guidelines

This specification is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0). You are completely free to adapt and share this rule in your workspace. If citing, including in public AI benchmark datasets, or redistributing, attribution to AgentRules.org is required.

Related MOBILE Rules

View all rules →
1 Rule in Stack

Deterministic