まずはコード
全てのコードは一番簡略的抽象化して一部切り抜けたもの
kmp側
abstract class Event : Parcelable {
var id: String = UUID.randomUUID()
internal set
}
sealed class EventBase : Event {
object EventB : Event()
data class EventA(name: String) : Event()
...
}
var event: Event
event = Event.EventA(name = "000")
event = Event.EventB
event = Event.EventB
Swift
public protocol StateMachineInterface {
associatedtype EventType
var event: Observable<EventType> { get }
}
var instance: StateMachineInterface ... // (eventをKMPから受け取る)
var event: Observable<EventType> {
return instance.map {
$0.event
}
.asObservable()
.observe(on: scheduler)
.share(replay: 1, scope: .whileConnected)
}
event
.distinctUntilChanged {
print("compare \($0),\($1)") // 調査するためにログを仕込んだ
return $0.id == $1.id
}
.subscribe {
print("subscribe \($0)")
...
}
...
出力結果
subscribe EventA(id=uuid1, name=000)
compare EventA(id=uuid1, name=000),EventB(id=uuid2)
subscribe EventB(id=uuid2)
compare EventB(id=uuid3),EventB(id=uuid3)
疑問
この出力結果でいくつかの疑問点が出てきます。
- EventBが連続で流れた場合
subscribe
に辿り着かない -
distinctUntilChanged
のbeforeとafterが1eventで同時に変更されている
解明
調べた末、Kotlin Multiplatformのobject型がiOS用にコンパイルされた時SingletonになることがKMPの公式に書いてあった
https://kotlinlang.org/docs/native-objc-interop.html#kotlin-singletons
これだけ知っていれば先ほどの挙動に解釈がつく
- EventBはobject型で宣言されているため、idだけが変わって実際はずっと同じinstance
- SingletonだからRxswiftの内部で参照型copy作ろうとしても結局同じinstanceが登録される
- EventBが連続で来たらbeforeとafterは同じaddressなので、afterのidが変わったらもちろんbeforeも一緒に変わる
結論
KMP使うならiOSエンジニアも一緒にKotlin勉強しましょう
参考リンク
https://kotlinlang.org/docs/object-declarations.html#object-declarations-overview
https://kotlinlang.org/docs/native-objc-interop.html#kotlin-singletons