Do not fear! This is not production code. It is just to learn new things about QML! I do not seek 'you shall not do something like this - do it like that.' I am more interested in the internals of QML
Consider the following QML-Code
import QtQuick 2.4
import QtQuick.Window 2.0
Window {
id: root
width: 800
height: 600
visible: true
GridView {
width: 800
height: 200
model: 4000
flow: GridView.FlowTopToBottom
delegate: Rectangle {
id: myDelegate
width: 100
height: 100
border.width: 1
Column {
anchors.fill: parent
Text {
text: index
height: 20
width: parent.width
}
Item {
id: frame0
height: 20
width: parent.width
}
Item {
id: frame1
height: 20
width: parent.width
}
}
Component.onCompleted: {
// if (index % 100 === 0) gc()
frame0.children = [myComp.createObject(myDelegate)]
frame1.children = [myComp.createObject(null)]
frame0.children[0].text = 'QML ' + index
frame1.children[0].text = 'JS ' + index
}
}
Component {
id: myComp
Text {
anchors.centerIn: parent
Component.onDestruction: console.log('Destroy ' + text)
}
}
}
}
It illustrates to some extent the MemoryManagement of QML when using dynamic ObjectCreation (JS).
I have a ListView
, that creates a few delegates, and lets me browse through them, creating new one on demand.
The trick is: When ever a new delegate is created, it uses the JavaScript
Dynamic Object Creation to create two instances of an text-object.
One of them is parented to the delegate, the other one is parented to null
and it's life is therefore determined by the JS-Engine. It should be garbage collected, once there is no pointer left, pointing towards it.
For the start, I will put both of them in a frame
(Item
), to display (setting a visual parent). As the delegate, those frames are destroyed.
This will - as expected - destroy the dynamically created Object that has the delegate as parent, as well. The other one is (should be) left for the Garbage Collector to do his work.
And this is where it fails - sometimes the application crashes before the GC kicks in, sometimes it crashes, while the GC is trying to do its work.
Though it is not reccommended by the documentation, it does help, to call the GC manualy (activate the line commented out in Component.onCompleted
).
So it seems to me, the GC overestimates it's abilities, and decides to kick in, when it is already to late.
What might be the reason for this? Is there a way to tell the GC to be mor proactive?
Again: I do not intend to use the Dynamic Object Creation with
.createObject(null)
in my code. It is pure curiosity.
The reason for this is the buggy qtquick object lifetime implementation. At this point it doesn't look to be a JS thing, but a qtquick thing. It clearly doesn't abide to its own alleged rules - for example, it will delete an object with a parent while still in use, resulting in a hard crash. You cannot expect reference counting to work as well. This behavior can occur in a number of scenarios that employ dynamism, it generally does not manifest in trivial and static scenarios.
The solution, as already outlined in the linked question, is to use manual object lifetime management. Use a set of new functions for the creation and deletion of objects.
QQmlEngine::setObjectOwnership(ojb, QQmlEngine::CppOwnership);
on it, this basically tells qtquick "don't even bother trying", luckily at least this works as it is supposedobj->deleteLater();
For me this does the trick, I no longer get crashes for no apparent reason. Use the custom lifetime management and stay away from the stock functions for that. It gives you guarantees, that the object will stay alive as long as you need it, but also that it will not stay past the point where you want it gone, which is another problem, albeit not that severe. Of course, this eliminates the convenience factor of using JS, as you have to give up on automatic lifetime management and be a little more diligent and explicit with your own code, but there isn't much you can do about it. Even though the bug was reported almost a year ago and deemed critical, not a shred of work has been done about it whatsoever. Thus I assume that as critical as it may be in its severity, it is more like lowest priority when it comes to finding its cause and fixing it.