Kotlin Multiplatform Mobile: Ktor - как отменить активную сопрограмму (сетевой запрос, фоновая работа) в Kotlin Native (iOS)?

В моем проекте я изначально использую View и ViewModel и использую репозиторий, Db, сеть .

Когда пользователь переходит с одного экрана на другой, я хочу отменить все сетевые запросы или другие тяжелые фоновые операции, которые в настоящее время выполняются на первом экране.

Пример функции в классе репозитория:

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

В ViewModel Android я могу использовать viewModelScope для автоматической отмены всех активных сопрограмм. Но как отменить эти задачи в приложении для iOS?


person Marat    schedule 27.01.2021    source источник
comment
вы можете использовать операцию отмены   -  person zeytin    schedule 28.01.2021
comment
@zeytin Не могли бы вы привести пример? Когда я пытался найти себя раньше, я не мог найти ничего   -  person Marat    schedule 28.01.2021
comment
конечно я уронил, это было полезно?   -  person zeytin    schedule 28.01.2021
comment
Привет, @Marat, расскажи, пожалуйста, статус этого вопроса?   -  person Artyom Degtyarev    schedule 04.03.2021
comment
@ArtyomDegtyarev Я не нашел существующего решения, поэтому придумал свое. Я разместил это как ответ. Пожалуйста, проверьте это.   -  person Marat    schedule 04.03.2021


Ответы (3)


Предположим, что сеанс объекта является экземпляром URLSession, вы можете отменить его:

session.invalidateAndCancel()
person zeytin    schedule 27.01.2021
comment
спасибо за ответ @zeytin. Боюсь, это не поможет. Мой вопрос касается проекта Kotlin Multiplatform, в котором сетевые запросы, фоновые задачи обрабатываются на стороне уровня данных и написаны на Kotlin. Возможно, они будут скомпилированы для использования собственных инструментов iOS, таких как URLSession, но у меня нет прямого упоминания об этом. Ищу что-нибудь на основе KMM - person Marat; 04.03.2021

У вас, вероятно, есть 3 варианта:

  • Если вы используете какую-то реактивную настройку на стороне iOS (например, MVVM), вы можете просто игнорировать отмену. Отмена позволит сэкономить минимальный объем работы.

  • Оберните свои вызовы iOS в общий код в реактивной среде iOS (например, объедините) и обработайте отмену с помощью платформы iOS. Совместная работа по-прежнему будет выполнена, но представление не будет обновлено, поскольку ваша платформа iOS обрабатывает отмену при выходе из экрана.

  • Используйте Flow с этот закрываемый помощник

person enyciaa    schedule 08.03.2021

Я не нашел никакой первичной информации об этом или какого-либо хорошего решения, поэтому я придумал свое. Вскоре потребуется преобразовать функции приостановки репозитория в обычные функции с типом возврата настраиваемого интерфейса, который имеет cancel() функцию-член. Функция примет действие лямбда в качестве параметра. На стороне реализации будет запущена сопрограмма, и ссылка на Job будет сохранена, поэтому позже, когда потребуется остановить фоновую работу, функция интерфейса cancel() отменит job.

Кроме того, поскольку очень трудно прочитать тип ошибки (в случае ее возникновения) из NSError, я заключил возвращаемые данные в специальный класс, который будет содержать сообщение об ошибке и тип. Ранее я задавал соответствующий вопрос, но не получил хорошего ответа для своего случая, когда 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))
    }
}

ErrorHandler:

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())
    }
}
person Marat    schedule 04.03.2021