Kotlin: safe lambdas (no hay fugas de memoria)?
Después de haber leído este artículo sobre fugas de memoria , me pregunto si el uso de lambdas en el proyecto Kotlin Android es seguro. Es cierto que la sintaxis lambda me hace programar con más facilidad, pero ¿qué pasa con las fugas de memoria?
Como ejemplo de la problemática, he tomado un pedazo de código de uno de mis proyectos, donde construyo un AlertDialog. Este código está dentro de la clase MainActivity de mi proyecto.
- Clueless Acerca de (posible) pérdida de memoria de Android
- Se detectó pérdida de memoria en las pestañas personalizadas de Chrome
- Fragmentos de Android en Backstack ocupan demasiada memoria
- ¿Está declarando una clase interna en una vista peligrosa?
- Informe Leakcanary de pérdida de memoria usando Otto
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 los dos lambdas establecidos para los botones positivos y negativos llevar a fugas de memoria? (También quiero decir, son kotlin lambdas simplemente convertido en Java Anónimo funciones?)
Edit: Tal vez tengo mi respuesta en este tema de Jetbrains .
- View.getViewTreeObserver (). AddOnGlobalLayoutListener Fragmento de fugas
- MapView v2 manteniendo el contexto alrededor
- Android Maps V2 pérdida de memoria LocationClientHelper
- ¿Cuáles son los beneficios de usar WeakReferences?
- ProgressDialog: cómo evitar la ventana filtrada
- Java.lang.OutOfMemoryError: android.support.v4.app.BackStackState de longitud 1279544898 se desbordaría
- Fuga de memoria en Android al intentar enviar un formulario con imagen al servidor PHP
- Destruir vista (disposición)
Edit (19 de febrero de 2017): Recibí una respuesta muy completa de Mike Hearn sobre este tema:
Al igual que en Java, lo que sucede en Kotlin varía en diferentes casos.
- Si el lambda se pasa a una función en línea y no está marcado noinline, entonces todo desaparece y no se crean clases u objetos adicionales.
- Si el lambda no captura, entonces se emitirá como una clase singleton cuya instancia se reutiliza una y otra vez (una clase + una asignación de objetos).
- Si el lambda captura, entonces se crea un nuevo objeto cada vez que se usa el lambda.
Por lo tanto, es un comportamiento similar a Java, excepto por el caso inline en el que es incluso más barato. Este enfoque eficiente para codificar lambdas es una razón por la 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 traigan algo nuevo a la mesa.
Son kotlin lambdas simplemente convertido en Java Anónimo funciones?
Estaba haciendo esta pregunta yo mismo (una simple corrección aquí: se llaman clases anónimas , no funciones). No hay una respuesta clara en la documentación de Koltin
. Simplemente afirman que
El uso de funciones de orden superior impone ciertas penalidades 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 significan las variables que se accede en el cuerpo de la función . ¿También se cuenta la referencia a la instancia de la clase inclusiva?
He visto el tema que está haciendo referencia en su pregunta, pero parece que está obsoleto por ahora. He encontrado más información actualizada aquí :
La expresión Lambda o la función anónima guardan una referencia implícita de la clase inclusiva
Por lo tanto, desafortunadamente, parece que los lambdas de Kotlin tienen los mismos problemas que las clases internas anónimas de Java.
¿Por qué las clases anónimas internas son malas?
Desde las especificaciones de Java
:
Una instancia i de una clase C interna directa de una clase O está asociada a una instancia de O, conocida como la instancia inmediata de i. La instancia de inclusión inmediata de un objeto, si la hay, 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 enclosing. Y puesto que la referencia está implícita no hay manera 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 se producirá la pérdida de memoria hasta que se ejecute la tarea de larga ejecución. Una solución para esto es utilizar clase anidada estática.
Dado que Kotlin's
lambdas no inline de Kotlin's
contienen la referencia a la instancia de la clase enclosing, tienen problemas similares con respecto a las pérdidas de memoria.
Bonus: Comparación rápida con otras implementaciones Lambda
Java 8 Lambdas
Sintaxis:
-
Declare SAM (solo método abstracto) interfaz
interface Runnable { void run(); }
-
Utilice esta interfaz como un tipo para un lambda
public void canTakeLambda(Runnable r) { ... }
-
Pase su lambda
canTakeLambda(() -> System.out.println("Do work in lambda..."));
Problemas de fugas de memoria: Como se indica en las especificaciones :
Las referencias a esto – incluyendo 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 el ejemplo apropiado de esto. En otros casos, ninguna referencia a esto es retenida por el objeto.
En pocas palabras, si no usas ningún campo / método de la clase que lo incluye, no hay una referencia implícita a this
como en el caso de las clases anónimas.
Retrolambda
De los documentos
Las expresiones Lambda son convertidas en clases internas anónimas. Esto incluye la optimización del uso de una instancia singleton para las expresiones lambda sin estado para evitar la asignación repetida de objetos.
Supongo que es autoexplicativo.
Rápido de Apple
Sintaxis:
-
La declaración es similar a Kotlin, en Swift los lambdas se llaman cierres:
func someFunctionThatTakesAClosure(closure: (String) -> Void) {}
-
Pasar el cierre
someFunctionThatTakesAClosure { print($0) }
Aquí,
$0
refiere al primer argumentoString
del cierre. Esto corresponde ait
en Kotlin. Nota: A diferencia de Kotlin, en Swift también podemos referirnos a otros argumentos como$1
,$2
etc.
Problemas de fugas de memoria:
En Swift, al igual que en Java 8, el cierre captura una referencia fuerte a self
( this
en Java y Kotlin) sólo 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()
.
También los desarrolladores pueden especificar fácilmente que sólo quieren capturar la referencia débil:
someFunctionThatTakesAClosure { [weak self] in print($0) }
Ojalá fuera posible en Kotlin también 🙂
Las fugas de memoria ocurren cuando algún objeto que debe ser removido porque no es necesario ya no puede ser eliminado porque algo que tiene una vida útil más larga tiene una referencia a este objeto. El ejemplo más sencillo 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 sí Se mantendrá en la memoria sin embargo – porque alguna variable estática todavía apunta a esta actividad.
Ahora, en su ejemplo, no está asignando su Activity
a alguna variable static
, no hay ningún object
de Kotlin involucrado que pueda impedir que su Activity
sea recolectada por la basura – todos los objetos involucrados en su código tienen aproximadamente la misma vida, lo que significa No habrá fugas de memoria.
PS He refrescado mis recuerdos en la implementación de Kotlin de los lambdas: en el caso del controlador de clics negativo, no estás haciendo referencia al ámbito externo, por lo que el compilador creará una instancia única del oyente de clics que se reutilizará en todos los Hace clic en este botón. En el caso del oyente de clic de botón positivo, estás haciendo 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 crees 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.
- ¿Cómo mostrar un mapa en una mapactividad de Android?
- ¿Cómo puedo cambiar el color de un gráfico complejo?