La recolección de basura QML elimina los objetos que aún están en uso
Me he encontrado con este problema en varias ocasiones, con objetos creados dinámicamente, independientemente de si se crearon en QML o C ++. Los objetos se eliminan mientras aún están en uso, lo que provoca accidentes graves sin razón aparente. Los objetos todavía están referenciados y pareados a otros objetos hasta el objeto raíz, por lo que me resulta extraño que QML elimine esos objetos mientras su recuento aún está por encima de cero.
Hasta ahora, la única solución que encontré fue crear los objetos en C ++ y establecer la propiedad en CPP explícitamente, lo que hace imposible eliminar los objetos de QML.
Al principio supuse que podría ser un problema con la crianza de los hijos, ya que estaba usandoQObject
clases derivadas, y el método QML de instanciación dinámica pasa unItem
para un padre, mientras queQtObject
ni siquiera viene con una propiedad principal, no está expuesta deQObject
.
Pero luego lo intenté con unQobject
derivado que expone y usa la crianza de los hijos y finalmente incluso intentó usarItem
solo por el hecho de estar seguro de que los objetos están correctamente pareados y, sin embargo, este comportamiento aún persiste.
Aquí hay un ejemplo que produce este comportamiento, desafortunadamente no pude aplanarlo a una sola fuente porque la anidación profunda deComponent
s lo rompe:
// ObjMain.qml
Item {
property ListModel list : ListModel { }
Component.onCompleted: console.log("created " + this + " with parent " + parent)
Component.onDestruction: console.log("deleted " + this)
}
// Uimain.qml
Item {
id: main
width: childrenRect.width
height: childrenRect.height
property Item object
property bool expanded : true
Loader {
id: li
x: 50
y: 50
active: expanded && object && object.list.count
width: childrenRect.width
height: childrenRect.height
sourceComponent: listView
}
Component {
id: listView
ListView {
width: contentItem.childrenRect.width
height: contentItem.childrenRect.height
model: object.list
delegate: Item {
id: p
width: childrenRect.width
height: childrenRect.height
Component.onCompleted: Qt.createComponent("Uimain.qml").createObject(p, {"object" : o})
}
}
}
Rectangle {
width: 50
height: 50
color: "red"
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton | Qt.LeftButton
onClicked: {
if (mouse.button == Qt.RightButton) {
expanded = !expanded
} else {
object.list.append({ "o" : Qt.createComponent("ObjMain.qml").createObject(object) })
}
}
}
}
}
// main.qml
Window {
visible: true
width: 1280
height: 720
ObjMain {
id: obj
}
Uimain {
object: obj
}
}
El ejemplo es un generador de árbol de objetos trivial, con el botón izquierdo agregando una hoja al nodo y el botón derecho contrayendo el nodo. Todo lo que se necesita para reproducir el error es crear un nodo con una profundidad de 3 y luego colapsar y expandir el nodo raíz, sobre el cual la salida de la consola muestra:
qml: created ObjMain_QMLTYPE_0(0x1e15bb8) with parent QQuickRootItem(0x1e15ca8)
qml: created ObjMain_QMLTYPE_0(0x1e5afc8) with parent ObjMain_QMLTYPE_0(0x1e15bb8)
qml: created ObjMain_QMLTYPE_0(0x1e30f58) with parent ObjMain_QMLTYPE_0(0x1e5afc8)
qml: deleted ObjMain_QMLTYPE_0(0x1e30f58)
losobject
del nodo más profundo se elimina sin motivo alguno, aunque esté parental al nodo principalItem
y referenciado en el objeto JS en el modelo de lista. Intentar agregar un nuevo nodo al nodo más profundo bloquea el programa.
El comportamiento es consistente, independientemente de la estructura del árbol, solo el segundo nivel de nodos sobrevive, todos los nodos más profundos se pierden cuando el árbol se colapsa.
La falla no radica en el modelo de lista que se usa como almacenamiento, lo he probado con una matriz JS y unQList
y los objetos todavía están perdidos. Este ejemplo utiliza un modelo de lista simplemente para guardar la implementación adicional de un modelo C ++. El único remedio que encontré hasta ahora fue negar la propiedad de QML de los objetos por completo. Aunque este ejemplo produce un comportamiento bastante consistente, en el código de producción las eliminaciones espontáneas son a menudo completamente arbitrarias.
Con respecto al recolector de basura, lo he probado antes y me di cuenta de que es bastante liberal: la creación y eliminación de objetos con un valor de 100 MB de ram no activó la recolección de basura para liberar esa memoria y, en este caso, solo unos pocos Los objetos, con un valor de unos cientos de bytes, se eliminan rápidamente.
De acuerdo con la documentación, los objetos que tienen un padre o que JS hace referencia no deben eliminarse, y en mi caso, ambos son válidos:
El objeto es propiedad de JavaScript. Cuando el objeto se devuelve a QML como el valor de retorno de una llamada al método, QML lo rastreará y lo eliminará si no hay referencias de JavaScript restantes y no tiene QObject :: parent ()
Como se menciona en la respuesta de Filip, esto no sucede si los objetos son creados por una función que no está en un objeto que se elimina, por lo que puede tener algo que ver con el estado JS vagamente mencionado asociado con los objetos QML, pero estoy esencialmente todavía en la oscuridad de por qué ocurre la eliminación, por lo que la pregunta sigue sin respuesta.
¿Alguna idea de que causa esto?
ACTUALIZAR: Nueve meses después, todavía no hay desarrollo eneste error crítico. Mientras tanto, descubrí varios escenarios adicionales donde se eliminan los objetos que todavía están en uso, escenarios en los que no importa dónde se creó el objeto y no se aplica la solución para crear simplemente los objetos en el archivo qml principal. La parte más extraña es que los objetos no se destruyen cuando se los "no hace referencia" sino que se los "vuelve a hacer referencia". Es decir, no se destruyen cuando los objetos visuales que los hacen referencia se destruyen, sino cuando se vuelven a crear.
La buena noticia es que todavía es posible establecer la propiedad en C ++ incluso para los objetos que se crean en QML, por lo que no se pierde la flexibilidad de la creación de objetos en QML. Existe el inconveniente menor de llamar a una función para proteger y eliminar cada objeto, pero al menos evita la gestión de errores de por vida de QtQuick. Sin embargo, tengo que amar la "conveniencia" de QML: ser obligado a volver amanual gestión de objetos de por vida.