Kotlin Multiplatform Mobile:Ktor - 如何在 Kotlin Native (iOS) 中取消活动协程(网络请求、后台工作)?
Kotlin Multiplatform Mobile: Ktor - how to cancel active coroutine (network request, background work) in Kotlin Native (iOS)?
在我的项目中,我编写了 View 和 ViewModel 并共享 Repository, Db, 网络.
当用户从一个屏幕导航到另一个屏幕时,我想取消第一个屏幕中当前 运行 的所有网络请求或其他繁重的后台操作。
存储库中的示例函数 class:
@Throws(Throwable::class)
suspend fun fetchData(): List<String>
在Android的ViewModel
中我可以使用viewModelScope
来自动取消所有活动协程。但是如何在 iOS 应用程序中取消这些任务?
假设对象会话是一个URLSession
实例,您可以通过以下方式取消它:
session.invalidateAndCancel()
我没有找到关于这个的任何第一方信息或任何好的解决方案,所以我想出了我自己的。很快,它将需要将存储库挂起函数转换为具有 return 类型自定义接口的常规函数,该自定义接口具有 cancel()
成员函数。函数将采取行动 lambda 作为参数。在实现方面,将启动协程并保留对 Job
的引用,以便稍后需要停止后台工作时,接口 cancel()
函数将取消 job
.
此外,因为很难从 NSError
中读取错误类型(以防万一),我用自定义 class 包装了 return 数据,这将保留错误消息和类型。早些时候我问过相关的 question 但对于 ViewModel
是在每个平台上本地编写的我的情况没有得到好的答案。
如果您发现此方法有任何问题或有任何想法,请分享。
自定义 return 数据包装器:
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 个选择:
如果您正在使用某种反应式设置 iOS 方面(例如 MVVM),您可以选择忽略取消。取消只会节省最少的工作量。
将对共享代码的 iOS 调用包装在 iOS 反应式框架(例如组合)中,并使用 iOS 框架处理取消。共享工作仍会完成,但视图不会更新,因为您的 iOS 框架在离开屏幕时正在处理取消。
使用 Flow
和 this closable helper
在我的项目中,我编写了 View 和 ViewModel 并共享 Repository, Db, 网络.
当用户从一个屏幕导航到另一个屏幕时,我想取消第一个屏幕中当前 运行 的所有网络请求或其他繁重的后台操作。
存储库中的示例函数 class:
@Throws(Throwable::class)
suspend fun fetchData(): List<String>
在Android的ViewModel
中我可以使用viewModelScope
来自动取消所有活动协程。但是如何在 iOS 应用程序中取消这些任务?
假设对象会话是一个URLSession
实例,您可以通过以下方式取消它:
session.invalidateAndCancel()
我没有找到关于这个的任何第一方信息或任何好的解决方案,所以我想出了我自己的。很快,它将需要将存储库挂起函数转换为具有 return 类型自定义接口的常规函数,该自定义接口具有 cancel()
成员函数。函数将采取行动 lambda 作为参数。在实现方面,将启动协程并保留对 Job
的引用,以便稍后需要停止后台工作时,接口 cancel()
函数将取消 job
.
此外,因为很难从 NSError
中读取错误类型(以防万一),我用自定义 class 包装了 return 数据,这将保留错误消息和类型。早些时候我问过相关的 question 但对于 ViewModel
是在每个平台上本地编写的我的情况没有得到好的答案。
如果您发现此方法有任何问题或有任何想法,请分享。
自定义 return 数据包装器:
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 个选择:
如果您正在使用某种反应式设置 iOS 方面(例如 MVVM),您可以选择忽略取消。取消只会节省最少的工作量。
将对共享代码的 iOS 调用包装在 iOS 反应式框架(例如组合)中,并使用 iOS 框架处理取消。共享工作仍会完成,但视图不会更新,因为您的 iOS 框架在离开屏幕时正在处理取消。
使用
Flow
和 this closable helper