Tim
Tim

Reputation: 4833

Why is QML deleting my C++ ListView model?

Using Qt 5.5.1 on iOS 9 I'm trying to assign a dynamically created QAbstractListModel to the model property of a ListView:

Window {
    ListView {
        model: api.model()
        delegate: delegate
    }
    Component {
        id: delegate
        Text { text: "Test" }
    }
}

api is a C++ object assigned to the QML context with setContextProperty. The model method is a Q_INVOKABLE which returns a QAbstractListModel *. This all works, my ListView is populated with data.

The problem is when I start scrolling. Usually after the second full scroll (to the bottom, back up to the top and down again) my ListView starts to clear itself out. The debugger is telling me the QAbstractListModel is being destroyed.

I don't want to set CppOwnership on the model. Is there another way to prevent the ListView from destroying its model?

Upvotes: 3

Views: 1311

Answers (4)

jkj yuio
jkj yuio

Reputation: 2613

I want to add something to ddriver's answer, which is more than a comment.

This same problem came up for me. basically, i wanted to create a dynamic list view model (QAbstractListModel, in fact). The usual way is to put your models up front in main (or somewhere) like this:

QQmlContext* ctxt = engine.rootContext();
ctxt->setContextProperty("myModel",  &model);

I have one model per object in this case, so i needed a dynamic solution.

I have a QObject which creates my model for a list. The model created derives from QAbstractListModel. The model is created and given out by my QObject host with a Q_INVOKABLE.

First problem is that the type of the model so generated is not known and must be registered. The usual qmlRegisterType does not work because QAbstractListModels cannot be copied. so you must register with qmlRegisterUncreatableType.

That's the first bit. Now the model works BUT who destroys it?

Turns out both my C++ code and QML both try to destroy the object since ownership was implicitly given to QML as part of the Q_INVOKABLE accessor.

BUT just letting QML clean up was bad. I tracked when this happened and it didn't happen at all in a timely manner. Basically it wouldn't clean up unless I did quite radial things like resize the window. presumably, it would eventually clean up (garbage etc.) but i really wanted these dynamic models to be cleaned up when their host QObject goes out.

So ddriver's idea is the way. but also remember to register with qmlRegisterUncreatableType.

eg,

inline MyModel* MyHostObject::getModel()
{
   if (!_model)
   {
     _model = new MyModel(this);

    // retain ownership of this object.
    QQmlEngine::setObjectOwnership(_model, QQmlEngine::CppOwnership);
   }
return _model;

}

Upvotes: 0

ls4f
ls4f

Reputation: 21

Just to say - I can confirm the same issue on both Linux x86_64 and Android ARMv7.

MyComponent {
    property var model: api.createModel()

    ListView {
        model: model
        delegate: delegate
        [...]
    }

    Component { id: delegate [...] }
}

Seems to be enough if you don't mind the model being destroyed later in time.

Upvotes: 1

Tim
Tim

Reputation: 4833

Even though I've accepted ddriver's answer I've found a solution that seems to better match what I wanted.

By dynamically loading my components and storing the model as a variable, I'm able to get QML to keep my C++ models alive and to destroy them when required, for example:

MyComponent {
    property var model: api.createModel()

    ListView {
        model: model
        delegate: delegate
        [...]
    }

    Component { id: delegate [...] }

    Component.onDestruction: model.destroy()
}

Unfortunately the model.destroy() call seems to be required. I was expecting the garbage collector to pick this up, but it doesn't seem to.

I've only tested this is toy examples so far, caveat lector.

Upvotes: 1

dtech
dtech

Reputation: 49329

QML seems kind of broken in this regard, I've experienced completely arbitrary deletions of objects still in use in multiple scenarios. Objects with parents and referenced by the JS engine are being deleted for no apparent reason while JS garbage still takes hundreds of megabytes of memory instead of being freed. This applies to both objects returned from C++ and objects created in QML. When an object is returned from a C++ function to QML, ownership is passed to the QML engine, which makes the object vulnerable to such arbitrary deletions.

The solution is to force CPP ownership and manage the object's lifetime manually - keep in mind destroy() won't work on such objects, so you have to use a C++ function from QML to delete it.

qmlEngine.setObjectOwnership(obj, QQmlEngine::CppOwnership);

Also, as BaCaRoZzo mentioned, exposing the model as a property to api might be the appropriate form. It depends on whether the function is just an accessor to an existing object or it creates the object itself.

At any rate, keep in mind that QML object lifetime management at this point cannot and should not be trusted.

Upvotes: 5

Related Questions