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
| Device | iOS Version | Result | Notes |
|---|---|---|---|
| iPhone 17 Pro | 26.1 | CRASHED | Had existing app data |
| iPhone 16e | 26.1 | WORKED | Fresh device, no data |
Same iOS version, different results → confirms data issue, not OS issue.
Resolution
Erase simulator data:
Via Xcode:
- Select the simulator device
- 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-EF0123456789Prevention
- Fresh simulators: When testing schema changes, use a fresh simulator
- Delete app first: Before installing updated builds, delete the app from the simulator
- 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 Type | Swift Type |
|---|---|
Long | Int64 |
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
- Select test target in Xcode
- Build Settings → Framework Search Paths
- Add
$(inherited)to the path list - Clean build folder (Cmd + Shift + K)
- 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
- Select test target in Xcode
- Build Settings → User Script Sandboxing
- Set to
No
Related Documentation
- KMP iOS Patterns - Integration patterns
- iOS Testing Strategy - Test configuration
- Equipment Comparison - KotlinLong example
Last Updated: 2025-12-01