Следует ли передавать coroutineScope в качестве аргумента функции? - PullRequest
2 голосов
/ 05 мая 2020

Я экспериментирую с сопрограммами и не уверен в передаче coroutineScope в простой Kotlin UseCase. Может ли такой подход вызвать утечку памяти?

Предположим, мы инициализируем наш UseCase в виртуальной машине и попытаемся передать viewModelScope :

class UploadUseCase(private val imagesPreparingForUploadUseCase: ImagesPreparingForUploadUseCase){

fun execute(coroutineScope: CoroutineScope, bitmap: Bitmap) {
        coroutineScope.launch {
            val resizedBitmap = withContext(Dispatchers.IO) {
                imagesPreparingForUploadUseCase.getResizedBitmap(bitmap, MAX_SIZE)
            }
        }
    }

}

Это безопасный код? Нет никакой разницы, если бы я объявил этот точный код в виртуальной машине? Если нет, это означает, что я могу передать coroutineScope в качестве аргумента конструктора ... Теперь я изначально подумал, что должен создать свой метод выполнения следующим образом:

fun CoroutineScope.execute(bitmap: Bitmap) {
        launch {
            val resizedBitmap = withContext(Dispatchers.IO) {
                imagesPreparingForUploadUseCase.getResizedBitmap(bitmap, MAX_SIZE)
            }
        }
    }

}

Насколько я понимаю, мы используем функцию расширения, чтобы метод использовал parent coroutineScope. Это означает, что мне не нужно передавать coroutineScope в качестве аргумента и просто изменять метод, чтобы использовать функцию расширения.

Однако, к моему удивлению, виртуальная машина не видит этот метод доступным! Почему этот метод недоступен для вызова из ВМ?

Он отмечен красным в ВМ:

 private fun uploadPhoto(bitmap: Bitmap, isImageUploaded: Boolean) {
        prepareDataForUploadingUseCase.execute(bitmap)
    }

Он не отмечен красным из ВМ:

 private fun uploadPhoto(bitmap: Bitmap, isImageUploaded: Boolean) {
        prepareDataForUploadingUseCase.execute(viewModelScope, bitmap)
    }

Если я неправильно понимаю, почему я должен использовать CoroutineScope как функцию расширения вместо передачи coroutineScope в качестве аргумента функции ?

Ответы [ 3 ]

3 голосов
/ 05 мая 2020

Передача его в качестве параметра или его использования в качестве приемника функции расширения фактически одинакова в конечном результате. Приемники функций расширения - это, по сути, еще один параметр, который вы передаете функции, только с измененным синтаксисом для удобства. Таким образом, вы не можете использовать функцию расширения как «обман», чтобы избежать передачи получателя. настройка внутри функции. Это приводит к распространению манипуляций с областью действия сопрограммы по обе стороны функционального барьера. Функция, вызывающая эту функцию, должна знать, что некоторая сопрограмма будет вызвана в той области видимости, которую она передает, но она не знает, нужно ли ей беспокоиться о том, как обрабатывать отмену и что ей разрешено делать с областью, которая он прошел.

На мой взгляд, было бы проще сделать следующее:

suspend fun execute(bitmap: Bitmap) = withContext(Dispatchers.IO) {
        imagesPreparingForUploadUseCase.getResizedBitmap(bitmap, MAX_SIZE)
    }

, чтобы вызывающая функция могла запускать сопрограмму и обрабатывать всю сопрограмму в одном месте. Или не передавайте область сопрограммы, но пусть функция execute внутренне генерирует свою собственную область (которая зависит от lifecycleScope или viewModelScope, если применимо) и обрабатывает свое собственное поведение отмены. Вот пример создания дочерней области области жизненного цикла и добавления ее к некоторой коллекции заданий, которые вы можете отменить при определенных обстоятельствах.

fun execute(bitmap: Bitmap) {
    lifecycleScope.launch {
        bitmapScopes += coroutineScope(Dispatchers.IO) {
            imagesPreparingForUploadUseCase.getResizedBitmap(bitmap, MAX_SIZE)
        }
    }
}
2 голосов
/ 05 мая 2020

Я отвечаю на этот конкретный c вопрос: «Почему этот метод недоступен из ВМ для вызова?»

Метод недоступен, потому что для него требуется приемник (CoroutineScope), но вы уже имеют неявный получатель из-за того, что он находится внутри объявления типа: UploadUseCase. Следовательно, вы не можете просто вызвать вторую форму метода, потому что вам так или иначе придется указать двух получателей.

К счастью, Kotlin предоставляет простой способ сделать именно это, with метод.

private fun uploadPhoto(bitmap: Bitmap, isImageUploaded: Boolean) {
    with(prepareDataForUploadingUseCase) {
        viewModelScope.execute(bitmap)
    }
}

Однако я бы сказал, что это довольно странно, и согласен с @Marko Novakovi c, что вы должны снять эту ответственность с UseCase.

2 голосов
/ 05 мая 2020

Вы можете передать CoroutineScope в качестве параметра функции, с этим нет проблем. Однако я бы посоветовал вам снять эту ответственность с UseCase. Запускаем сопрограммы из ViewModel, Presenter и c. Функции расширения должны вызываться для экземпляра типа расширения. Вам не нужно вызывать launch {} и withContext внутри одной функции. Сделай либо. launch(Dispatchers.IO) {}. Функции расширения предназначены не только для доступа к родительской области видимости, вы можете использовать их для всего, что вам нужно, по вашему выбору.

...