iOS Troubleshooting Guide

Common issues and resolutions for iOS development with KMP.


App Crashes on Launch with ObjCExportCoroutines.kt

Symptom

App crashes immediately on launch with:

EXC_BAD_ACCESS (SIGSEGV) at ObjCExportCoroutines.kt
Kotlin_ObjCExport_runCompletionFailure

Root Cause

NOT an iOS version incompatibility issue. The actual cause is stale or corrupted simulator database data.

When KMP Room database schema changes during development, old data in the simulator can become incompatible. When a DAO method (like roundDao.getAllRoundsSnapshot()) attempts to deserialize corrupted data, the ObjCExportCoroutines bridge crashes.

Evidence

DeviceiOS VersionResultNotes
iPhone 17 Pro26.1CRASHEDHad existing app data
iPhone 16e26.1WORKEDFresh device, no data

Same iOS version, different results → confirms data issue, not OS issue.

Resolution

Erase simulator data:

Via Xcode:

  1. Select the simulator device
  2. Device menu → Erase All Content and Settings

Via Terminal:

# List all simulators
xcrun simctl list devices
 
# Erase specific simulator by UDID
xcrun simctl erase <UDID>
 
# Example:
xcrun simctl erase 5A7B9C3D-1234-5678-ABCD-EF0123456789

Prevention

  1. Fresh simulators: When testing schema changes, use a fresh simulator
  2. Delete app first: Before installing updated builds, delete the app from the simulator
  3. Database migrations: Consider adding migration tests to catch schema drift early

KMP Enum Comparison in Swift

Symptom

Filtering or switching on KMP enums doesn’t work as expected:

// This doesn't work as expected
if tournament.status == TournamentStatus.completed { ... }

Root Cause

Kotlin enums are reference types in Swift. Direct equality comparison checks reference identity, not value equality.

Resolution

Compare using the .name property:

// ✅ Correct
if tournament.status.name == TournamentStatus.completed.name { ... }
 
// ❌ Wrong
if tournament.status == TournamentStatus.completed { ... }

Pattern for switch statements:

switch tournament.status.name {
case TournamentStatus.open.name:
    return "Open"
case TournamentStatus.inProgress.name:
    return "In Progress"
case TournamentStatus.completed.name:
    return "Completed"
default:
    return "Unknown"
}

KotlinLong Interop for Nullable Long?

Symptom

Build error when passing Swift Int64? to KMP method expecting Long?:

Cannot convert value of type 'Int64?' to expected argument type 'KotlinLong?'

Root Cause

KMP generates different Swift types for nullable vs non-nullable Long:

Kotlin TypeSwift Type
LongInt64
Long?KotlinLong?

Resolution

Wrap optional Int64? in KotlinLong:

// For non-nullable Long with default value in KMP:
let effectiveStartDate: Int64 = optionalStartDate ?? 0
dao.method(startDate: effectiveStartDate)
 
// For nullable Long? in KMP:
let kotlinStartDate: KotlinLong? = optionalStartDate.map { KotlinLong(value: $0) }
dao.method(startDate: kotlinStartDate)

Example from EquipmentComparisonBridge:

func getEquipmentDriftStats(setupId: Int64, startDate: Int64?) async throws -> EquipmentDriftStats? {
    // KMP signature: startDate: Long? = null (nullable)
    let kotlinStartDate: KotlinLong? = startDate.map { KotlinLong(value: $0) }
    return try await roundDao.getEquipmentDriftStats(bowSetupId: setupId, startDate: kotlinStartDate)
}

iOS Test Target Framework Search Paths

Symptom

Test target cannot find KMP modules:

No such module 'Shared'

Root Cause

iOS test targets require $(inherited) in Framework Search Paths for CocoaPods modules.

Resolution

  1. Select test target in Xcode
  2. Build Settings → Framework Search Paths
  3. Add $(inherited) to the path list
  4. Clean build folder (Cmd + Shift + K)
  5. Build again

Firebase Callback Pattern for KMP

Symptom

App crashes or hangs when using Swift async/await with KMP coroutines in production.

Root Cause

KMP’s ObjCExportCoroutines bridge can be unstable when combined with Swift’s async/await in certain scenarios, especially with Firebase callbacks.

Resolution

Use Firebase callback pattern instead of async/await for production code:

// ❌ Problematic: async/await with KMP types
func loadTournaments() async {
    let tournaments = try await repository.getPublicTournaments()
    // May crash with ObjCExportCoroutines error
}
 
// ✅ Stable: Callback pattern
func loadFromFirebase() {
    isLoading = true
 
    db.collection("tournaments")
        .whereField("public", isEqualTo: true)
        .getDocuments { [weak self] (querySnapshot, error) in
            Task { @MainActor in
                // Process results safely
                if let documents = querySnapshot?.documents {
                    let tournaments = documents.compactMap { TournamentParser.parse(document: $0) }
                    self?.tournaments = tournaments
                }
                self?.isLoading = false
            }
        }
}

Key pattern:

  • Use Firebase’s native callback API
  • Wrap result processing in Task { @MainActor in ... }
  • Keep async/await for test mocks only

User Script Sandboxing

Symptom

Test target build fails with CocoaPods script errors.

Root Cause

Xcode’s User Script Sandboxing interferes with CocoaPods scripts.

Resolution

  1. Select test target in Xcode
  2. Build Settings → User Script Sandboxing
  3. Set to No


Last Updated: 2025-12-01