icerockdev / moko-kswift

Swift-friendly api generator for Kotlin/Native frameworks
https://moko.icerock.dev
Apache License 2.0
350 stars 21 forks source link

Provide Swift friendly alternative to data class copy methods #61

Closed dalewking closed 8 months ago

dalewking commented 2 years ago

Similar to #8, but requires a different solution There is no good Swift way to call a copy methods on a Kotlin data class because the default parameter values are not provided, but it isn't possible to actually have parameters default to values based on the object so it is not possible to write an exact replica of the copy function. It isn't important that what is generated be the same syntactically. It is more important to get the same functionality, e.g to make a copy of the data class changing only part of the object without having to specify every property.

Here is a SO post that talks about how to write such a method in Dart, which I know is not Swift, but the same concepts and limitations apply: https://stackoverflow.com/questions/68009392/dart-custom-copywith-method-with-nullable-properties

The best alternative is probably the one that takes nullable parameters that default to nil and if it is nil the property is unchanged. The wrinkle is for properties that were nullable, in which case you add a boolean parameter for each such property that says whether to treat null as null or unchanged.

ema987 commented 1 year ago

I was thinking about the same feature and found this issue.

I've been working to accomplish this and I think I have got something at a good level but still needs some refinement that needs some discussion.

Given a data class like:

data class DataClass(
    val stringValue: String,
    val optionalStringValue: String?,
    val intValue: Int,
    val optionalIntValue: Int?,
    val booleanValue: Boolean,
    val optionalBooleanValue: Boolean?
)

the plugin will generate the following extension function for Swift

extension MultiPlatformLibrary.DataClass {

  func betterCopy(
    stringValue: (() -> Swift.String)? = nil,
    optionalStringValue: (() -> Swift.String?)? = nil,
    intValue: (() -> Swift.Int32)? = nil,
    optionalIntValue: (() -> MultiPlatformLibrary.KotlinInt?)? = nil,
    booleanValue: (() -> Swift.Bool)? = nil,
    optionalBooleanValue: (() -> MultiPlatformLibrary.KotlinBoolean?)? = nil
  ) -> MultiPlatformLibrary.DataClass {
    return MultiPlatformLibrary.DataClass(stringValue: (stringValue != nil) ? stringValue!() : self.stringValue,optionalStringValue: (optionalStringValue != nil) ? optionalStringValue!() : self.optionalStringValue,intValue: (intValue != nil) ? intValue!() : self.intValue,optionalIntValue: (optionalIntValue != nil) ? optionalIntValue!() : self.optionalIntValue,booleanValue: (booleanValue != nil) ? booleanValue!() : self.booleanValue,optionalBooleanValue: (optionalBooleanValue != nil) ? optionalBooleanValue!() : self.optionalBooleanValue)
  }

}

For each constructor argument of the data class there is a closure parameter in the betterCopy function (yeah, we might need to discuss about its name!). I decided to use a closure to also support optional parameters, so it works this way:

A small example of how it can be used:

let dataClass = DataClass(
            stringValue: "aValue",
            optionalStringValue: nil,
            intValue: 0,
            optionalIntValue: 1,
            booleanValue: false,
            optionalBooleanValue: nil
            )
        let dataClassWithNewValues = dataClass.betterCopy(stringValue: {"aNewValue"}, optionalIntValue: {nil}, booleanValue: {true})

dataClassWithNewValues will have new values for:

See the related PR for more examples.

I think we need to discuss it with @Alex009 if the idea is cool and can be integrated officially into this library.

Alex009 commented 1 year ago

@ema987 it's intresting way, i will check pr later