android - pass - kotlin lambda unit
Kotlin: lambdas seguros(sin pérdida de memoria)? (2)
Después de haber leído este artículo sobre Memory Leaks , me pregunto si usar lambdas en el proyecto Kotlin para Android es seguro. Es cierto que la sintaxis lambda me hace programar con más facilidad, pero ¿qué pasa con las pérdidas de memoria?
Como ejemplo de la problemática, tomé un fragmento de código de uno de mis proyectos, donde construí un AlertDialog. Este código está dentro de la clase MainActivity de mi proyecto.
fun deleteItemOnConfirmation(id: Long) : Unit {
val item = explorerAdapter.getItemAt(id.toInt())
val stringId = if (item.isDirectory) R.string.about_to_delete_folder else R.string.about_to_delete_file
val dialog = AlertDialog.Builder(this).
setMessage(String.format(getString(stringId), item.name)).setPositiveButton(
R.string.ok, {dialog: DialogInterface, id: Int ->
val success = if (item.isDirectory) ExplorerFileManager.deleteFolderRecursively(item.name)
else ExplorerFileManager.deleteFile(item.name)
if (success) {
explorerAdapter.deleteItem(item)
explorerRecyclerView.invalidate()
}
else Toast.makeText(this@MainActivity, R.string.file_deletion_error, Toast.LENGTH_SHORT).show()
}).setNegativeButton(
R.string.cancel, {dialog: DialogInterface, id: Int ->
dialog.cancel()
})
dialog.show()
}
Mi pregunta es muy simple: ¿pueden las dos lambdas configuradas para los botones positivo y negativo conducir a Memory Leaks? (También quiero decir, ¿las kotlin lambdas simplemente se convierten en funciones Java anónimas?)
Editar: Tal vez tengo mi respuesta en este tema de Jetbrains .
Las pérdidas de memoria ocurren cuando algún objeto que debe eliminarse porque ya no es necesario no se puede eliminar porque algo que tiene una vida más larga tiene una referencia a este objeto. El ejemplo más simple es almacenar la referencia a la Activity
en la variable static
(estoy hablando desde la perspectiva de Java, pero es similar en Kotlin): después de que el usuario ha hecho clic en el botón "Atrás" la Activity
ya no es necesaria, pero Sin embargo, se mantendrá en la memoria porque algunas variables estáticas aún apuntan a esta actividad.
Ahora, en su ejemplo, no está asignando su Activity
a una variable static
, no hay ningún object
de Kotlin involucrado que pueda evitar que su Activity
se recolecte basura. Todos los objetos involucrados en su código tienen aproximadamente la misma duración, lo que significa no habrá pérdidas de memoria.
PD. He refrescado mis recuerdos sobre la implementación de las lambdas de Kotlin: en el caso del manejador de clic negativo, no hace referencia al ámbito externo, por lo tanto, el compilador creará una sola instancia del escucha de clic que se reutilizará en todas las hace clic en este botón. En el caso del botón positivo haga clic en oyente, se hace referencia al ámbito externo ( this@MainActivity
), por lo que en este caso Kotlin creará una nueva instancia de la clase anónima cada vez que cree un diálogo (y esta instancia tendrá la referencia a la clase externa, MainActivity
), por lo que el comportamiento es exactamente el mismo que si hubiera escrito este código en Java.
Editar (19 de febrero de 2017): recibí una respuesta muy completa de Mike Hearn con respecto a este tema:
Al igual que en Java, lo que sucede en Kotlin varía en diferentes casos.
- Si la lambda pasa a una función en línea y no está marcada como no lineal, todo se evapora y no se crean clases u objetos adicionales.
- Si la lambda no captura, se emitirá como una clase singleton cuya instancia se reutiliza una y otra vez (una clase + una asignación de objeto).
- Si la lambda captura, se crea un nuevo objeto cada vez que se utiliza la lambda.
Por lo tanto, es un comportamiento similar al de Java, excepto en el caso en línea, donde es incluso más barato. Este enfoque eficiente para codificar lambdas es una de las razones por las que la programación funcional en Kotlin es más atractiva que en Java.
Editar (17 de febrero de 2017): He publicado una pregunta sobre este tema en las discusiones de Kotlin . Tal vez los ingenieros de Kotlin traerán algo nuevo a la mesa.
¿Los kotlin lambdas simplemente se convierten en funciones Java anónimas?
Yo mismo estaba haciendo esta pregunta (una corrección simple aquí: se llaman clases anónimas , no funciones). No hay una respuesta clara en la documentación de Koltin
. Ellos solo dicen eso
El uso de funciones de orden superior impone ciertas penalizaciones de tiempo de ejecución: cada función es un objeto y captura un cierre, es decir, aquellas variables a las que se accede en el cuerpo de la función.
Es un poco confuso lo que quieren decir con las variables a las que se accede en el cuerpo de la función . ¿También se cuenta la referencia a la instancia de la clase adjunta?
He visto el tema al que hace referencia en su pregunta, pero parece que está desactualizado por ahora. Encontré más información actualizada aquí :
La expresión lambda o la función anónima conservan una referencia implícita de la clase adjunta
Por lo tanto, desafortunadamente, parece que las lambdas de Kotlin tienen los mismos problemas que las Clases internas anónimas de Java.
¿Por qué las clases internas anónimas son malas?
De las especificaciones de Java
:
Una instancia i de una clase interna directa C de una clase O se asocia con una instancia de O, conocida como la instancia inmediatamente cerrada de i. La instancia de inclusión inmediata de un objeto, si existe, se determina cuando se crea el objeto
Lo que esto significa es que la clase anónima siempre tendrá una referencia implícita a la instancia de la clase adjunta. Y dado que la referencia está implícita, no hay forma de deshacerse de ella.
Mira el ejemplo trivial
public class YourActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new Thread(new Runnable() {
// the inner class will keep the implicit reference to the outer activity
@Override
public void run() {
// long-running task
}
}).start();
}
}
Como puede ver, en este caso habrá una pérdida de memoria hasta que se ejecute la tarea de larga ejecución. Una solución para esto es usar una clase anidada estática.
Como Kotlin''s
lambdas no en línea de Kotlin''s
contienen la referencia a la instancia de la clase envolvente, tienen problemas similares con respecto a las pérdidas de memoria.
Bonificación: Comparación rápida con otras implementaciones de Lambda
Java 8 Lambdas
Sintaxis:
Interfaz Declare SAM (método abstracto único)
interface Runnable { void run(); }
Use esta interfaz como un tipo para una lambda
public void canTakeLambda(Runnable r) { ... }
Pasa tu lambda
canTakeLambda(() -> System.out.println("Do work in lambda..."));
Problemas de pérdida de memoria: como se indica en las especificaciones :
Las referencias a esto, incluidas las referencias implícitas a través de referencias de campo no calificadas o invocaciones de métodos, son, esencialmente, referencias a una variable local final. Los cuerpos lambda que contienen tales referencias capturan la instancia apropiada de esto. En otros casos, el objeto no retiene ninguna referencia a esto .
En pocas palabras, si no utiliza ningún campo / método de la clase adjunta, no hay ninguna referencia implícita a this
como en el caso de las clases anónimas.
Retrolambda
De los documentos
Las expresiones Lambda se transfieren convirtiéndolas a clases internas anónimas. Esto incluye la optimización del uso de una instancia singleton para expresiones lambda sin estado para evitar la asignación repetida de objetos.
Supongo que es autoexplicativo.
Swift de Apple
Sintaxis:
La declaración es similar a Kotlin, en Swift lambdas se llaman cierres:
func someFunctionThatTakesAClosure(closure: (String) -> Void) {}
Pase el cierre
someFunctionThatTakesAClosure { print($0) }
Aquí,
$0
refiere al primer argumento deString
del cierre. Esto le corresponde en Kotlin. Nota: A diferencia de Kotlin, en Swift también podemos referirnos a otros argumentos como$1
,$2
etc.
Problemas de pérdida de memoria:
En Swift, al igual que en Java 8, el cierre captura una fuerte referencia a self
( this
en Java y Kotlin) solo si accede a una propiedad de la instancia, como self.someProperty
, o si el cierre llama a un método en la instancia , como self.someMethod()
.
Además, los desarrolladores pueden especificar fácilmente que desean capturar solo la referencia débil:
someFunctionThatTakesAClosure { [weak self] in print($0) }
Ojalá fuera posible en Kotlin también :)