又是被 KMM 吊起来锤的一个月。
上次提到 MVIKotlin 能用却难用,毕竟 Jetpack AAC 太好用了。于是我决定手动把 Jetpack AAC 移植到 KMM。由于 LiveData 可以使用 kotlinx.coroutines 的 StateFlow/SharedFlow 代替,所以理论上只需移植 Lifecycle 和 ViewModel 即可,Lifecycle 通过注解某个函数即可让该函数在相应生命周期执行时即可运行的功能也要暂时阉割,因为注解处理器要能在 KMM 平台使用必须使用 KCP,这个比较困难,可以先往后放放。
这样算下来一共是阉割版的 Lifecycle 与 ViewModel 两个库需要移植。我希望移植版本的公有 API 与 Jetpack 版本尽可能一致,最好还能完全兼容 Jetpack。那基本的移植方式就是,先在 Common source set 用 expect 声明公有 API,如果 API 是顶层函数/静态方法,则在 Android source set 的 actual 实现中直接调用 Jetpack 版本就行了,在 iOS source set 的 actual 实现中可以直接仿写 Jetpack 的实现代码,然后把平台相关的代码魔改即可。如果 API 是类/接口等类型声明,在 Android source set 的 actual 实现中直接用 typealias 等价桥接到 Jetpack 内的类型即可,而在 iOS source set 中仍然是仿写 Jetpack 的声明与实现代码重新写一份。从理论上来说,由于类型都用 typealias 桥接到了 Jetpack 内的声明,那么无论是编译期还是运行时 Common 层的 Lifecycle 与 ViewModel 都与 Jetpack 内完全等价,也我们可以省略很多实现代码,例如 Fragment 以及 ComponentActivity 这些实现了 ViewModelStoreOwner 等接口的平台相关组件。
理论虽然是很理想化的,但是实现起来就遇到了两个个无解的问题:
1. Jetpack Lifecycle 与 ViewModel public API 会引用强平台相关类型
例如 Java 反射的 Class>:
public open class ViewModelProvider(private val store: ViewModelStore, private val factory: Factory)
{
/**
* Implementations of `Factory` interface are responsible to instantiate ViewModels.
*/
public interface Factory {
/**
* Creates a new instance of the given `Class`.
*
* @param modelClass a `Class` whose instance is requested
* @return a newly created ViewModel
*/
public fun <T : ViewModel> create(modelClass: Class<T>): T
}
例如 ViewModelProvider 的 Factory,create 函数就需要用到 Class>,为了能够在 KMM 中与其尽量保持一致,我们只能把 Class 改为 Kotlin 的 KClass<*>。但是一旦改写,我们就无法通过 typealias 桥接 Factory,如果我们自己定义 Factory 接口,与 Jetpack 的兼容性也将受到破坏。
2. 反射在 Kotlin/Native 上不可用
上面提到 Jetpack 需要 Class> 对象,其根本目的是使用反射,例如使用反射创建对象:
// actually there is getInstance()
@Suppress("SingletonConstructor")
public open class NewInstanceFactory : Factory {
@Suppress("DocumentExceptions")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return try {
modelClass.newInstance()
} catch (e: InstantiationException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
}
catch (e: IllegalAccessException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
}
}
在 Kotlin 中,反射仅在 JVM 平台完全支持,在 JS 平台有限支持,而在 Native 平台几乎没有支持。所以由于反射功能的缺失,许多 API 的设计要完全改变。例如创建对象可能需要将一个构造函数或工厂函数作为参数。
我们该怎么做?
最初我们想实现兼容 Jetpack 的目的无非有三:
既然兼容已经几乎不可能实现,我们可以放弃兼容性只追求易用性。所以方案改为参照 Lifecycle 及 ViewModel 的设计自行编写一套 KMM 版本的架构组件。
这里先开个坑,等后面在 Github 上开个项目,如果思路大至可行,再记录更新。