Search code examples
swiftkotlin-multiplatform

KMM - Casting Sealed Classes in Swift


I'm fairly new to KMM/Swift and am struggling to cast sealed classes.

I have the following class:

data class DataPointState(val id: Long? = null){
 sealed class MultiSelectOptions<T> {
        data class Strings(val options: List<UserGeneratedString>) :
            MultiSelectOptions<List<UserGeneratedString>>()
        data class Lifts(val options: List<Lift>) : MultiSelectOptions<List<Lift>>()

       
    }
 data class EditState(
        val value: InputData? = null
    ) {
        sealed class InputData { 
             data class MultiSelectOptions(val list: DataPointState.MultiSelectOptions<*>) :
                InputData()
             //More data classes here
    }
}

On the Android side, I can type check/cast my Input data to MultiSelectOptions, and then type check/cast to Lifts or Strings:

 when (value) {     
   ...          
    is DataPointState.EditState.InputData.MultiSelectOptions -> FlowRow(
            horizontalArrangement = Arrangement.spacedBy(4.dp)
        ) {
            value.list.let {
                when (it) {
                    is DataPointState.MultiSelectOptions.Lifts -> it.options.forEach {
                        ...
                    }

                    is DataPointState.MultiSelectOptions.Strings -> it.options.forEach {
                        ...
                    }
                }
            }
        } 

But when I try to do the equivalent in Swift I get a warning Cast from 'DataPointStateMultiSelectOptions<AnyObject>' to unrelated type 'DataPointStateMultiSelectOptionsLifts' always fails

  switch value {  
          case let value as DataPointState.EditStateInputDataMultiSelectOptions:
           
                switch value.list {
                case let list as DataPointStateMultiSelectOptionsLifts :
                    Text("test")
                default:
                    EmptyView()
                }
                
            }

Any help figuring this out is greatly appreciated!


Solution

  • Swift doesn't have an equivalent of the star project you used in MultiSelectOptions<*>.

    It turns out MultiSelectOptions<*> got translated to MultiSelectOptions<AnyObject>, kind of similar to MultiSelectOptions<Any> in Kotlin. As you may know, MultiSelectOptions<Any> is unrelated to MultiSelectOptions.Lifts, so that's why you got the warning.

    As far as I know, Kotlin interops with Swift by turning its generics to Objective-C's lightweight generics first. At this point, MultiSelectOptions<*> is turned into MultiSelectOptions<id>. And finally it gets imported into Swift as MultiSelectOptions<AnyObject>.

    Objective-C's lightweight generics are also "erased" similar to how Kotlin's are. There is no runtime mechanism to enforce that MultiSelectOptions<AnyObject> can't be a MultiSelectOptions<[Lift]>.

    If you cast to AnyObject first, and then cast to Lift, you can bypass Swift's strict enforcement of generic types.

    switch value.list as AnyObject {
    case let list as DataPointStateMultiSelectOptionsLifts:
        Text("test")
    default:
        EmptyView()
    }