您应该将 coroutineScope 作为函数参数传递吗?

Vik*_*kov 5 kotlin android-mvvm kotlin-coroutines kotlin-coroutines-flow

我正在试验协程,但不确定是否将 coroutineScope 传递给普通的 Kotlin 用例。这种方法会造成内存泄漏吗?

假设我们正在 VM 中初始化我们的 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)
            }
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

是安全码吗?如果我在 VM 中声明这个确切的代码没有区别吗?如果不是,那意味着我可以将 coroutineScope 作为构造函数参数传递......现在我最初认为我应该通过以下方式创建我的 execute 方法:

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

}
Run Code Online (Sandbox Code Playgroud)

据我所知,我们使用扩展函数以便方法使用父协程范围。这意味着,我不需要将 coroutineScope 作为参数传递,只需更改方法即可使用扩展函数。

但是,令我惊讶的是,VM 看不到此方法可用!为什么这个方法不能从VM调用?

这在 VM 中标记为红色:

 private fun uploadPhoto(bitmap: Bitmap, isImageUploaded: Boolean) {
        prepareDataForUploadingUseCase.execute(bitmap)
    }
Run Code Online (Sandbox Code Playgroud)

这在 VM 中未标记为红色:

 private fun uploadPhoto(bitmap: Bitmap, isImageUploaded: Boolean) {
        prepareDataForUploadingUseCase.execute(viewModelScope, bitmap)
    }
Run Code Online (Sandbox Code Playgroud)

如果我的理解是错误的,我为什么要用 CoroutineScope 作为扩展函数 而不是将 coroutineScope 作为函数参数传递

Ten*_*r04 6

将其作为参数传递与将其用作扩展函数接收器在最终结果中实际上是相同的。扩展函数接收器基本上是传递给函数的另一个参数,只是为了方便而重新排列了语法。所以你不能使用扩展函数作为“作弊”来避免传递接收者。

但无论哪种方式,我都认为这是一种笨拙的设计,必须提供一个范围,然后将协程设置隐藏在函数内。这导致协程作用域操作分散到函数屏障的两侧。调用此函数的函数必须意识到某些协程将在其传递的范围内被调用,但它不知道是否需要担心如何处理取消以及允许对该范围执行什么操作它过去了。

在我看来,这样做会更干净:

suspend fun execute(bitmap: Bitmap) = withContext(Dispatchers.IO) {
        imagesPreparingForUploadUseCase.getResizedBitmap(bitmap, MAX_SIZE)
    }
Run Code Online (Sandbox Code Playgroud)

因此调用函数可以启动协程并在一个地方处理整个协程。或者不传递协程作用域,但让execute函数在内部生成自己的作用域(依赖于lifecycleScopeviewModelScope如果适用),并处理自己的取消行为。以下是创建生命周期范围的子范围并将其添加到您在某些情况下可能想要取消的某些作业集合的示例。

fun execute(bitmap: Bitmap) {
    lifecycleScope.launch {
        bitmapScopes += coroutineScope(Dispatchers.IO) {
            imagesPreparingForUploadUseCase.getResizedBitmap(bitmap, MAX_SIZE)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 看来我最后与协程库团队负责人的建议相矛盾:https://medium.com/@elizarov/coroutine-context-and-scope-c8b255d59055 他实际上建议将范围作为函数参数或接收器作为在签名中指示该函数启动“即发即忘”类型协程的一种方式。 (4认同)

Oct*_*ami 2

我正在回答这个具体问题:“为什么VM无法调用此方法?”

该方法不可用,因为它需要一个接收器 ( CoroutineScope),但由于位于类型声明内,您已经有了一个隐式接收器:UploadUseCase。因此,您不能只调用该方法的第二种形式,因为您必须以某种方式指定两个接收者。

幸运的是,Kotlin 提供了一种简单的方法来做到这一点,即with方法。

private fun uploadPhoto(bitmap: Bitmap, isImageUploaded: Boolean) {
    with(prepareDataForUploadingUseCase) {
        viewModelScope.execute(bitmap)
    }
}
Run Code Online (Sandbox Code Playgroud)

然而,我想说这很奇怪,并且同意 @Marko Novakovic 的观点,即你应该从UseCase.