【问题标题】:Kotlin Multiplatform Mobile: Ktor - how to cancel active coroutine (network request, background work) in Kotlin Native (iOS)?Kotlin Multiplatform Mobile:Ktor - 如何在 Kotlin Native(iOS)中取消活动协程(网络请求、后台工作)?
【发布时间】:2021-05-01 18:32:49
【问题描述】:

在我的项目中,我以原生方式编写 ViewViewModel 并共享 RepositoryDbnetworking

当用户从一个屏幕导航到另一个屏幕时,我想取消当前在第一个屏幕上运行的所有网络请求或其他繁重的后台操作。

Repository 类中的示例函数:

@Throws(Throwable::class)
suspend fun fetchData(): List<String>

在 Android 的 ViewModel 中,我可以使用 viewModelScope 自动取消所有活动的协程。但是如何在 iOS 应用中取消这些任务呢?

【问题讨论】:

  • 可以使用取消操作
  • @zeytin 你能提供一些例子吗?在我找不到任何东西之前试图找到自己时
  • 确定我放弃了,有用吗?
  • 你好,@Marat,你能告诉我这个问题的状态吗?
  • @ArtyomDegtyarev 我没有找到任何现有的解决方案,所以我想出了自己的解决方案。我将其发布为答案。请检查一下。

标签: android ios kotlin-multiplatform ktor kotlin-native


【解决方案1】:

假设对象会话是URLSession 实例,您可以通过以下方式取消它:

session.invalidateAndCancel()

【讨论】:

  • 感谢您的回答@zeytin。恐怕它不会有帮助。我的问题是关于 Kotlin Multiplatform 项目,其中网络请求、后台任务在数据层端处理,并且是用 Kotlin 编写的。也许它们被编译以使用 URLSession 等 iOS 原生工具,但我没有直接引用它。我正在寻找基于 KMM 的东西
【解决方案2】:

我没有找到任何关于此的第一方信息或任何好的解决方案,所以我想出了自己的。很快,它将需要将存储库挂起函数转换为返回类型为具有cancel() 成员函数的自定义接口的常规函数​​。函数将采取行动 lambda 作为参数。在实现方面,将启动协程并保留Job的引用,以便稍后需要停止后台工作接口时cancel()函数将取消job

此外,由于很难从NSError 读取错误类型(以防万一),我使用自定义类包装了返回数据,该类将保存错误消息和类型。早些时候我问过相关的question,但没有得到很好的答案,因为ViewModel 是在每个平台上本地编写的。

如果您发现此方法有任何问题或有任何想法,请分享。

自定义返回数据包装器:

class Result<T>(
    val status: Status,
    val value: T? = null,
    val error: KError? = null
)

enum class Status {
    SUCCESS, FAIL
}

data class KError(
    val type: ErrorType,
    val message: String? = null,
)

enum class ErrorType {
    UNAUTHORIZED, CANCELED, OTHER
}

自定义界面

interface Cancelable {
    fun cancel()
}

存储库接口:

//Convert this code inside of Repository interface:

@Throws(Throwable::class)
suspend fun fetchData(): List<String>

//To this:

fun fetchData(action: (Result<List<String>>) -> Unit): Cancelable

存储库实现:

override fun fetchData(action: (Result<List<String>>) -> Unit): Cancelable = runInsideOfCancelableCoroutine {
    val result = executeAndHandleExceptions {
        val data = networkExample()
        // do mapping, db operations, etc.
        data
    }

    action.invoke(result)
}

// example of doing heavy background work
private suspend fun networkExample(): List<String> {
    // delay, thread sleep
    return listOf("data 1", "data 2", "data 3")
}

// generic function for reuse
private fun runInsideOfCancelableCoroutine(task: suspend () -> Unit): Cancelable {

    val job = Job()

    CoroutineScope(Dispatchers.Main + job).launch {
        ensureActive()
        task.invoke()
    }

    return object : Cancelable {
        override fun cancel() {
            job.cancel()
        }
    }
}

// generic function for reuse
private suspend fun <T> executeAndHandleExceptions(action: suspend () -> T?): Result<T> {
    return try {
        val data = action.invoke()
        Result(status = Status.SUCCESS, value = data, error = null)
    } catch (t: Throwable) {
        Result(status = Status.FAIL, value = null, error = ErrorHandler.getError(t))
    }
}

错误处理程序:

object ErrorHandler {

    fun getError(t: Throwable): KError {
        when (t) {
            is ClientRequestException -> {
                try {
                    when (t.response.status.value) {
                        401 -> return KError(ErrorType.UNAUTHORIZED)
                    }
                } catch (t: Throwable) {

                }
            }
            is CancellationException -> {
                return KError(ErrorType.CANCELED)
            }
        }
        return KError(ErrorType.OTHER, t.stackTraceToString())
    }
}

【讨论】:

    【解决方案3】:

    您可能有 3 个选项:

    • 如果您使用某种响应式设置 iOS 端(例如 MVVM),您可以选择忽略取消。取消只会节省最少的工作量。

    • 将您对共享代码的 iOS 调用封装在一个 iOS 响应式框架(例如 combine)中,并使用 iOS 框架处理取消操作。共享的工作仍会完成,但视图不会更新,因为您的 iOS 框架正在处理离开屏幕时的取消。

    • Flowthis closable helper 一起使用

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-06
      • 2021-10-02
      • 2021-07-29
      • 2019-04-30
      • 1970-01-01
      相关资源
      最近更新 更多