This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
mobilecritical PriorityID: kmp-swift-export-arc-boundary-pinning

Kotlin Multiplatform 2.1 Swift-Export ARC Boundary Pinning

Enforces strict lifecycle management, explicit object pinning, and cycle decoupling across the Kotlin/Native and Apple Swift-Export ARC boundary in KMP 2.1+. Prevents catastrophic native heap corruption and undetectable retain cycles caused by bridge-crossing closures, strong delegate references, and implicit Kotlin StableRef handling under Swift Automatic Reference Counting.

Prohibited Anti-Pattern: Implicit Bidirectional Retain Cycles and Premature Object Deallocation across Swift-Export Boundaries

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: Kotlin Multiplatform 2.1 Swift-Export ARC Boundary Pinning - Enforces strict lifecycle management, explicit object pinning, and cycle decoupling across the Kotlin/Native and Apple Swift-Export ARC boundary in KMP 2.1+. Prevents catastrophic native heap corruption and undetectable retain cycles caused by bridge-crossing closures, strong delegate references, and implicit Kotlin StableRef handling under Swift Automatic Reference Counting.
globs: shared/**/commonMain/**/*.kt, shared/**/iosMain/**/*.kt, iosApp/**/*.swift
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/kmp-swift-export-arc-boundary-pinning"
---

# Kotlin Multiplatform 2.1 Swift-Export ARC Boundary Pinning

> **Severity**: CRITICAL | **Category**: mobile
> **Anti-Pattern Warning**: Implicit Bidirectional Retain Cycles and Premature Object Deallocation across Swift-Export Boundaries

## System Constraints
You are a Principal Mobile Architect enforcing absolute memory safety and deterministic lifecycle constraints on Kotlin Multiplatform 2.1+ projects utilizing Swift-Export. You must eliminate all direct bidirectional retain cycles between Swift ARC and Kotlin/Native runtime tracking. Enforce weak listener patterns, explicit unpinning protocols, and safe boundary bridging to prevent both silent memory leaks and use-after-free native segmentation faults.

## Strict Guidelines
- Engineers MUST ALWAYS implement an explicit lifecycle disposal contract (e.g., `Disposable`, `Closeable`) on any Kotlin class exported to Swift that registers long-lived native callbacks or listeners.
- Engineers MUST NOT capture strong Kotlin object references inside escaping Swift closures or delegate properties directly exposed through Swift-Export.
- Swift code MUST ALWAYS mark delegate references to Kotlin-exported interfaces or bridging protocol conformances with `weak` or wrap them in an explicit `WeakRef` container.
- Engineers MUST NEVER use raw `StableRef` pointers across public Swift-Export interfaces without wrapping them in managed RAII lifecycle constructs that guarantee single-ownership balance.
- Shared Kotlin Coroutine scopes crossing the boundary via Swift-Export MUST be cancelled deterministically inside Swift view deinitializers (`deinit`) or UIViewController lifecycle dismissals to break implicit scope retaining trees.

## Bad vs Good Implementations
### Decoupling Swift ARC Observer from Kotlin Coroutine-Backed Store

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Kotlin holds a strong reference to the Swift listener implementation, while the Swift consumer holds a strong ARC reference to AppStateStore. This cross-boundary circular reference cannot be resolved by ARC (which sees only an opaque Kotlin object handle) or by Kotlin's GC (which sees an active root via the bridge), causing permanent memory leaks.
```kotlin
// shared/src/iosMain/kotlin/com/platform/AppStateStore.kt
package com.platform

interface StateListener {
    fun onStateChange(state: String)
}

class AppStateStore {
    // ANTI-PATTERN: Strong reference to Swift listener retained in Kotlin heap.
    // When Swift observer holds strong reference to AppStateStore,
    // a cross-runtime cycle forms that neither ARC nor Kotlin GC can collect.
    private var listener: StateListener? = null

    fun setListener(listener: StateListener) {
        this.listener = listener
    }

    fun emit(state: String) {
        listener?.onStateChange(state)
    }
}
```

#### Production Standard (Correct)
> **Standard**: The Kotlin side preserves a `WeakReference` to the listener and returns a deterministic `Cancellable` token. The Swift consumer manages its own ARC lifecycle without being rooted by the Kotlin engine, breaking the cross-runtime circular dependency.
```kotlin
// shared/src/iosMain/kotlin/com/platform/AppStateStore.kt
package com.platform

import kotlin.native.ref.WeakReference

interface StateListener : Any

interface Cancellable {
    fun cancel()
}

class AppStateStore {
    private var weakListener: WeakReference<StateListener>? = null

    fun setListener(listener: StateListener): Cancellable {
        this.weakListener = WeakReference(listener)
        return object : Cancellable {
            override fun cancel() {
                weakListener = null
            }
        }
    }

    fun emit(state: String) {
        // Boundary-safe resolution: Kotlin Native weak reference dereferencing
        val activeListener = weakListener?.get()
        if (activeListener is StateListenerCallback) {
            activeListener.onStateChange(state)
        } else if (weakListener?.get() == null) {
            weakListener = null
        }
    }
}

interface StateListenerCallback {
    fun onStateChange(state: String)
}
```

### Swift-Export Callback Handling with Explicit ARC Weak Capture

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Registering `self` directly to an exported Kotlin store without lifetime bounding pins the Swift object across the ARC boundary. If `AppStateStore` outlives the SwiftUI view hierarchy, the entire view model remains allocated in the heap.
```swift
// iosApp/iOSApp/ViewModelWrapper.swift
import SwiftUI
import SharedExportedCore

class UserProfileViewModel: ObservableObject {
    private let coreStore: AppStateStore

    init(coreStore: AppStateStore) {
        self.coreStore = coreStore
        // ANTI-PATTERN: Escaping closure strongly captures `self` across the bridge.
        // Swift-Export retains this closure in Kotlin memory, pinning `self` permanently.
        coreStore.setListener(listener: self)
    }
}

extension UserProfileViewModel: StateListenerCallback {
    func onStateChange(state: String) {
        print("Received: \(state)")
    }
}
```

#### Production Standard (Correct)
> **Standard**: Uses an intermediate `StateTrampoline` with a `weak` ARC reference back to the view model combined with deterministic `cancel()` invocation in `deinit`. This guarantees that Swift ARC retains ownership of its view model while preventing Kotlin from pinning Swift native memory.
```swift
// iosApp/iOSApp/ViewModelWrapper.swift
import SwiftUI
import SharedExportedCore

final class UserProfileViewModel: ObservableObject {
    private let coreStore: AppStateStore
    private var subscriptionToken: Cancellable?

    init(coreStore: AppStateStore) {
        self.coreStore = coreStore
        bind()
    }

    private func bind() {
        // Invariant: Trampoline wrapper or weak bridging prevents ARC pinning
        let trampoline = StateTrampoline(target: self)
        self.subscriptionToken = coreStore.setListener(listener: trampoline)
    }

    deinit {
        // Explicit unpinning boundary: Deterministic cleanup breaks cross-runtime pins
        subscriptionToken?.cancel()
    }
}

private final class StateTrampoline: StateListenerCallback {
    private weak var target: UserProfileViewModel?

    init(target: UserProfileViewModel) {
        self.target = target
    }

    func onStateChange(state: String) {
        target?.handleStateChange(state)
    }
}

extension UserProfileViewModel {
    fileprivate func handleStateChange(_ state: String) {
        // Handle state safely within ARC lifecycle
    }
}
```

Architectural Specification & Invariant Rationale

Architectural Invariant Design: Kotlin Multiplatform 2.1 Swift-Export ARC Boundary Pinning

Kotlin Multiplatform 2.1 Swift-Export ARC Boundary Pinning defines an industrial-grade behavioral standard for mobile engineering. Enforces strict lifecycle management, explicit object pinning, and cycle decoupling across the Kotlin/Native and Apple Swift-Export ARC boundary in KMP 2.1+. Prevents catastrophic native heap corruption and undetectable retain cycles caused by bridge-crossing closures, strong delegate references, and implicit Kotlin StableRef handling under Swift Automatic Reference Counting. 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: Implicit Bidirectional Retain Cycles and Premature Object Deallocation across Swift-Export Boundaries 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 DriftImplicit Bidirectional Retain Cycles and Premature Object Deallocation across Swift-Export BoundariesCritical
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 implement an explicit lifecycle disposal contract (e.g., `Disposable`, `Closeable`) on any Kotlin class exported to Swift that registers long-lived native callbacks or listeners.

Engineers MUST ALWAYS implement an explicit lifecycle disposal contract (e.g., `Disposable`, `Closeable`) on any Kotlin class exported to Swift that registers long-lived native callbacks or listeners.

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 capture strong Kotlin object references inside escaping Swift closures or delegate properties directly exposed through Swift-Export.

Engineers MUST NOT capture strong Kotlin object references inside escaping Swift closures or delegate properties directly exposed through Swift-Export.

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

Swift code MUST ALWAYS mark delegate references to Kotlin-exported interfaces or bridging protocol conformances with `weak` or wrap them in an explicit `WeakRef` container.

Swift code MUST ALWAYS mark delegate references to Kotlin-exported interfaces or bridging protocol conformances with `weak` or wrap them in an explicit `WeakRef` container.

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

Engineers MUST NEVER use raw `StableRef` pointers across public Swift-Export interfaces without wrapping them in managed RAII lifecycle constructs that guarantee single-ownership balance.

Engineers MUST NEVER use raw `StableRef` pointers across public Swift-Export interfaces without wrapping them in managed RAII lifecycle constructs that guarantee single-ownership balance.

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

Shared Kotlin Coroutine scopes crossing the boundary via Swift-Export MUST be cancelled deterministically inside Swift view deinitializers (`deinit`) or UIViewController lifecycle dismissals to break implicit scope retaining trees.

Shared Kotlin Coroutine scopes crossing the boundary via Swift-Export MUST be cancelled deterministically inside Swift view deinitializers (`deinit`) or UIViewController lifecycle dismissals to break implicit scope retaining trees.

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 Kotlin Multiplatform 2.1 Swift-Export ARC Boundary Pinning critical for AI coding assistants?

Enforces strict lifecycle management, explicit object pinning, and cycle decoupling across the Kotlin/Native and Apple Swift-Export ARC boundary in KMP 2.1+. Prevents catastrophic native heap corruption and undetectable retain cycles caused by bridge-crossing closures, strong delegate references, and implicit Kotlin StableRef handling under Swift Automatic Reference Counting. 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 (Implicit Bidirectional Retain Cycles and Premature Object Deallocation across Swift-Export Boundaries) 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

Kotlin