QML垃圾收集删除仍在使用的对象
QML garbage collection deletes objects still in use
我曾多次遇到这个问题,无论对象是在QML还是C++中创建的,都是动态创建的。这些对象在使用中被删除,导致无明显原因的严重崩溃。这些对象仍然是其他对象的引用对象和父对象,一直到根对象,所以我觉得QML在它们的refcount仍然高于零的情况下删除这些对象很奇怪。
到目前为止,我找到的唯一解决方案是在C++中创建对象,并显式地将所有权设置为CPP,从而无法从QML中删除对象。
起初,我认为这可能是养育子女的问题,因为我使用的是QObject
派生类,并且动态实例化的QML方法为父级传递Item
,而QtObject
甚至没有父级属性-它没有从QObject
中公开。
但后来我尝试了Qobject
派生,它公开并使用父子关系,最后甚至尝试使用Item
,只是为了确保对象是正确的父子关系,但这种行为仍然存在。
这里有一个产生这种行为的例子,不幸的是,我无法将其扁平化为单个源,因为Component
的深度嵌套破坏了它:
// 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
}
}
这个例子是一个简单的对象树生成器,左按钮向节点添加一个叶子,右按钮折叠节点。重现错误所需要的只是创建一个深度为3的节点,然后折叠和扩展根节点,控制台输出显示:
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)
最深节点的object
被无故删除,即使它是父节点Item
的父节点,并且在列表模型中的JS对象中被引用。试图将新节点添加到最深的节点会使程序崩溃。
行为是一致的,无论树的结构如何,只有第二级节点存活,当树折叠时,所有更深的节点都会丢失。
故障不在于用作存储的列表模型,我已经用JS数组和QList
进行了测试,但对象仍然丢失。本例使用列表模型只是为了保存C++模型的额外实现。到目前为止,我找到的唯一补救办法是完全拒绝QML对这些对象的所有权。尽管这个例子产生了相当一致的行为,但在生产代码中,自发的删除通常是完全任意的。
关于垃圾回收器——我以前测试过它,注意到它非常自由——创建和删除100 MB内存的对象并没有触发垃圾回收来释放内存,但在这种情况下,只有几个几百字节的对象被匆忙删除。
根据文档,具有父对象或被JS引用的对象不应被删除,在我的情况下,两者都是有效的:
该对象由JavaScript所有。当对象返回QML时作为方法调用的返回值,QML将跟踪并删除它如果没有剩余的JavaScript引用,并且没有Q对象::parent()
正如Filip的回答中所提到的,如果对象是由一个不在被删除对象中的函数创建的,则不会发生这种情况,因此这可能与模糊提及的与QML对象相关的JS状态有关,但我基本上仍然不知道为什么会发生删除,因此这个问题实际上仍然没有答案。
有什么原因吗?
更新:九个月后,这个关键错误的开发仍然为零。同时,我发现了一些其他场景,其中删除了仍在使用的对象,在这些场景中,对象是在哪里创建的并不重要,简单地在主qml文件中创建对象的解决方法也不适用。最奇怪的是,当对象被"取消引用"时,它们并没有被破坏,而是被"重新引用"时。也就是说,当引用它们的视觉对象被破坏时,它们并没有被破坏,而是在重新创建时。
好消息是,即使是在QML中创建的对象,也可以将所有权设置为C++,因此QML中对象创建的灵活性不会丢失。调用一个函数来保护和删除每个对象会带来一些小的不便,但至少可以避免QtQuick的错误寿命管理。尽管如此,我还是喜欢QML的"便利性"——被迫回到手动对象生命周期管理。
我曾多次遇到这个问题,对象是动态创建的,无论它们是在QML还是C++中创建的
只有当对象设置了JavaScriptOwnership时,才考虑对其进行垃圾收集,如果对象是,则情况就是这样
- 由JavaScript/QML直接创建
- 所有权明确设置为JavaScriptOwnership
- 该对象是从Q_INVOKABLE方法返回的,并且以前没有对其调用setObjectOwnership()
在所有其他情况下,对象都假定为C++所有,而不考虑进行垃圾收集。
起初,我认为这可能是养育子女的问题,因为我使用的是QObject派生的类,并且动态实例化的QML方法为父对象传递一个Item,而QtObject甚至没有父属性-它不是从QObject中公开的。
Qt对象树与Qml对象树完全不同。QML只关心它自己的对象树。
delegate: Item {
id: p
width: childrenRect.width
height: childrenRect.height
Component.onCompleted: Qt.createComponent("Uimain.qml").createObject(p, {"object" : o})
}
委托的onCompleted处理程序中动态创建的对象的组合必然会导致错误。
当您折叠树时,代理将被销毁,并随之销毁其所有子对象,其中包括动态创建的对象。如果仍然有关于孩子们的现场参考资料,那也没关系。
从本质上讲,您没有为树提供稳定的后备存储——它由一堆嵌套的委托组成,这些委托可以随时消失。
现在,在某些情况下,QML拥有的对象被意外删除:任何C++引用都不算作垃圾收集器的引用;这包括Q_PROPERTY。在这种情况下,您可以:
- 显式设置CppOwnership
- 使用QPointer<>保持引用以处理正在消失的对象
- 在QML中保持对对象的显式引用
QML在管理内存方面不是C++。QML的目的是负责分配内存和释放内存。我认为您发现的问题就是这个问题的结果。
如果动态对象创建过于深入,则所有内容似乎都会被删除。因此,您创建的对象是数据的一部分并不重要,它们也会被销毁。
不幸的是,我的知识到此为止。
解决这个问题的方法之一(证明了我之前的陈述)是将数据结构的创建从动态UI qml文件中移出:
- 例如,将对象创建函数放在main.qml中
function createNewObject(parentObject) {
parentObject.list.append({ "o" : Qt.createComponent("ObjMain.qml").createObject(parentObject) })
}
- 在代码中使用此函数:
// fragment of the Uimain.qml file
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton | Qt.LeftButton
onClicked: {
if (mouse.button == Qt.RightButton) {
expanded = !expanded
} else {
createNewObject(object)
}
}
}
在.js文件中创建一个数组,然后在该.js.
文件的顶层创建一个具有var myArray = [];
的数组实例。
现在,您可以引用附加到myArray
的任何对象,包括动态创建的对象。
只要Javascript变量保持定义状态,它们就不会被垃圾收集删除,所以如果您将其定义为全局对象,然后将该Javascript文件包含在qml文档中,那么只要主qml在作用域中,它就会一直保留。
在一个名为:backend.js的文件中
var tiles = [];
function create_square(new_square) {
var component = Qt.createComponent("qrc:///src_qml/src_game/Square.qml");
var sq = component.createObject(background, { "backend" : new_square });
sq.x = new_square.tile.x
sq.y = new_square.tile.y
sq.width = new_square.tile.width;
sq.height = new_square.tile.height;
tiles[game.board.getIndex(new_square.tile.row, new_square.tile.col)] = sq;
sq.visible = true;
}
编辑:
让我更清楚地解释一下这如何应用于您的特定树示例。
通过使用行property Item object
,您无意中将其强制为Item的属性,QML中对此有不同的处理。具体来说,就垃圾回收而言,属性属于一组唯一的规则,因为QML引擎可以简单地开始删除任何对象的属性,以减少运行所需的内存。
相反,在QML文档的顶部,包括以下行:
import "./object_file.js" as object_file
然后在object_file.js文件中,包括以下行:
var object_hash = [];
现在,您可以随时使用object_hash
来保存动态创建的组件,并通过引用来防止它们被删除
object_file.object_hash
对象。
无需疯狂更改所有权等
- 在不知道深度或父属性的情况下从对象中删除属性
- 解析JSON并从中删除对象会出错
- 如何从对象中删除属性
- 在特定条件下从存储在localStorage中的阵列中删除对象
- Es6:能够在设置/更新/删除对象属性时调用自定义方法
- AngularJS:删除重复的对象并按值对数组进行重新排序
- 正在从对象中删除值
- 正在从列表中删除对象
- 删除对象时终止setInterval
- jQuery-检测选择对象是否添加或删除了选项
- 如何在不删除类似侦听器的情况下从父对象中删除jQuery事件侦听器
- 为什么没有从数组中存在的对象中删除属性
- 从Three.js场景中删除许多对象的速度较慢
- 删除两个独立对象中的属性,而不使用多余的删除语句
- JS:将 EventListener 添加到动态创建的 DOM 对象中,这些对象删除 parentNode
- Java 脚本 json 解析对象:删除操作后不删除“,”
- JavaScript对象删除特定键值中的引号
- tinyMCE对象删除事件触发器
- javascript地图对象删除不工作
- Javascript对象删除元素