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

Should you pass coroutineScope as function argument?

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

假设我们正在 VM 中初始化我们的用例,并将尝试通过 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)
            }
        }
    }

}

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

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

}

据我了解,我们使用扩展函数以便方法使用 parent coroutineScope。 这意味着,我不需要将 coroutineScope 作为参数传递,只需更改方法使用扩展功能。

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

这在 VM 中被标记为红色:

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

这不是来自 VM 的红色标记:

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

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

您可以将 CoroutineScope 作为函数参数传递,没问题。但是,我建议您从 UseCase 中删除该责任。从 ViewModelPresenter 等启动协程。 扩展函数将在扩展类型的实例上调用。您不需要在同一函数内调用 launch {}withContext。做任何一个。 launch(Dispatchers.IO) {}。 扩展函数不仅仅是为了访问父作用域,您可以选择将它们用于任何您需要的用途。

我正在回答这个具体问题:"Why this method is not available from VM to call?"

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

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

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

但是,我会说这很奇怪,并且同意@Marko Novakovic 的观点,您应该从 UseCase 中删除此责任。

将其作为参数传递与将其用作扩展函数接收器在最终结果上实际上是相同的。扩展函数接收器基本上是您传递给函数的另一个参数,只是为了方便使用了重新排列的语法。因此,您不能将扩展函数用作 "cheat" 来避免传递接收器。

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

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

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

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

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