在 SwiftUI 中使用 Compose 共享业务逻辑

前言

看到标题也许会一头雾水,SwiftUI、Compose、业务逻辑这几个词分开看得懂,连起来是个什么玩意?

首先,关于 Compose 和业务逻辑的关系,在之前的文章提到过:Compose 和 Compose UI 有些不同,Compose 可以完全不包含 UI,结合 Compose 的特性,你甚至拿 Compose 来写业务逻辑也完全没有问题。有不了解的朋友可以先看看之前的文章,在这里就不赘述了。

那么更进一步,Compose 编写的业务逻辑能不能用在 SwiftUI 中呢?

答案是可以的!

下面就来讲讲如何在 iOS 甚至其他更多平台使用 Compose 编写的业务逻辑代码,从而达到多个平台共享同一套业务逻辑代码。

如何在 SwiftUI 中使用 Compose 编写的业务逻辑

之前的文章有提到 Jake Wharton 大佬编写的 Molecule,简单说就是一个使用 Compose 的特性来生成 Flow 的一个工具,在这个工具的帮助下,我们就可以编写一套共享的业务逻辑。

还是举 Counter 的例子来说,我们首先定义一个 CounterState 和CounterPresenter:

Kotlin 复制代码
@Composable
fun counterPresenter(): CounterState {
    var count by remember { mutableStateOf(0) }
    return object : CounterState(count.toString()) {
        override fun increment() {
            count++
        }
    }
}

abstract class CounterState(
    val count: String,
) {
    abstract fun increment()
}

当然,在 iOS 中,counterPresenter 是无法直接调用的,这时候就需要使用 Molecule 来进行桥接,输出一个 Flow 给 iOS 使用:

Kotlin 复制代码
class CounterPresenter {
    private val scope = CoroutineScope(Dispatchers.Main + DisplayLinkClock)

    actual val models: StateFlow<CounterState> by lazy {
        scope.launchMolecule(mode = RecompositionMode.ContextClock) {
            counterPresenter()
        }
    }
}

在 iOS 中,Flow 并不是很容易直接调用,这时候需要 SKIE 来帮我们将 Flow 的调用简化,这样我们就可以在 iOS 中这样调用 CounterPresenter

Swift 复制代码
@Observable
class CounterViewModel {
    private let presenter = CounterPresenter()
    var model: CounterState
    init() {
        // 初始化 model的值
        model = presenter.models.value
    }
    @MainActor
    func activate() async {
        // 借助 SKIE 完成对 CounterPresenter.models 的监听
        for await model in presenter.models {
            self.model = model
        }
    }
}

struct CounterView: View {
    @State var viewModel = CounterViewModel()
    var body: some View {
        VStack {
            // 直接使用 CounterState 的值
            Text("Counter: \(viewModel.model.count)")
            Button(action: {
                // 直接调用 CounterState 内的方法
                viewModel.model.increment()
            }, label: {
                Text("Increment")
            })
        }
        // 确保 viewModel 监听正常 Presenter 的 Flow
        .task {
            await viewModel.activate()
        }
    }
}

而在 Android 中,我们可以直接在 Compose 中使用 counterPresenter,相关的内容已经在前文有介绍过,就不再赘述了。

限制

当然这样的做法也是有一些限制的,毕竟 Kotlin/Native 还是一个很初期的阶段

范型

Kotlin/Native 在 iOS 上还不是直接与 Swift 进行交互,还只是通过 Objective-C 来进行的,这个过程中泛型就很容易出问题,几乎所有的 interface 定义的泛型都无法使用,但是 class 可以,举个例子:

Kotlin 复制代码
interface SomeInterface<T> {}
val i: SomeInterface<T> // 在 Swift 中 i 的泛型会被抹除

class SomeClass<T> {}
val c: SomeClass<T> // 而 c 的泛型能够正确使用

最典型的例子就是 List<T>,我们需要返回一个 class 来避免泛型类型被抹除,需要手写一个类似 ListWrapper 的 class,并且不能让这个 class 继承 List<T>

生命周期

SwiftUI 和 Compose UI 的生命周期还是有一些差别的,比如在 iOS 中,当页面返回之后,我们的 CounterPresenter 似乎会被重新创建,导致数据会被重新获取,如果是严格使用单一数据源的架构的话,这倒不是什么大问题。不过我对于 SwiftUI 的了解还是非常浅显的,有可能是我理解错误,这里就抛砖引玉,还请各位 iOS 开发来解答比较好。

FAQ

Q:这样做有什么好处吗?

一定会有人遇到因为平台表现不一致而导致的问题,相同的一套业务逻辑代码能够同时在多端共享,在保证业务逻辑一致的情况下还能拥有 Native 级别的性能,并且还能与 Swift 代码交互。

Q:都用上 Compose 了,我直接用 Compose 画 UI 不是更好吗?

确实,这更多是一种选择。如果更偏向体验一致可以选择直接用 Compose 画 UI,如果想要 Native 的体验,还需要多端业务逻辑保持一致,那么文章提到的做法也不失为一种选择。

Q:这些限制这么多,感觉会有很多坑,而且不稳定

确实,Kotlin Multiplatform 甚至目前为止都还不是正式版,仍然有不少基础组件缺失,写一些业务逻辑其实也会遇到因为基础组件的缺失而导致很别扭的情况,比如 HTML 解析,所以文章提到的更多是一种尝试。

Q:说了这么些,有没有什么实际项目在这样用呢?

您好,有的。这是我最近一直在写的一个项目:github.com/DimensionDe... ,这是一个跨平台的 Mastodon/Misskey/Bluesky 的社交客户端,支持 Android/iOS/macOS,代码层面完全是按照文章所说的实现的,核心业务逻辑完全使用 Kotlin 编写,在 Android 上使用 Jetpack Compose 实现 UI,在 iOS/macOS 上使用 SwiftUI,目前也进入了公开测试阶段,欢迎体验。

是的,为了写这篇文章,我特地写了一个项目(狗头)。

另外这篇文章从去年9月拖到现在终于写完了。

相关推荐
游戏开发爱好者83 小时前
日常开发与测试的 App 测试方法、查看设备状态、实时日志、应用数据
android·ios·小程序·https·uni-app·iphone·webview
王码码20353 小时前
Flutter for OpenHarmony 实战之基础组件:第三十一篇 Chip 系列组件 — 灵活的标签化交互
android·flutter·交互·harmonyos
黑码哥4 小时前
ViewHolder设计模式深度剖析:iOS开发者掌握Android列表性能优化的实战指南
android·ios·性能优化·跨平台开发·viewholder
亓才孓4 小时前
[JDBC]元数据
android
独行soc4 小时前
2026年渗透测试面试题总结-17(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
金融RPA机器人丨实在智能4 小时前
Android Studio开发App项目进入AI深水区:实在智能Agent引领无代码交互革命
android·人工智能·ai·android studio
科技块儿4 小时前
利用IP查询在智慧城市交通信号系统中的应用探索
android·tcp/ip·智慧城市
独行soc5 小时前
2026年渗透测试面试题总结-18(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
王码码20355 小时前
Flutter for OpenHarmony 实战之基础组件:第二十七篇 BottomSheet — 动态底部弹窗与底部栏菜单
android·flutter·harmonyos
2501_915106325 小时前
app 上架过程,安装包准备、证书与描述文件管理、安装测试、上传
android·ios·小程序·https·uni-app·iphone·webview