Я использую QSignalMapper с Qt 4.8. Сейчас я делаю сетевые запросы, как показано ниже:
// start the request
QNetworkRequest request(url);
QNetworkReply* reply = networkManager->get(request);
// connect signals using QSignalMapper
QSignalMapper* signalMapper = new QSignalMapper(reply);
connect(signalMapper, SIGNAL(mapped(QObject*)),this, SLOT(onFeedRetrieved(QObject*)), Qt::UniqueConnection);
connect(reply,SIGNAL(finished()),signalMapper, SLOT(map())); // connect to the signal mapper
signalMapper->setMapping(reply, dataModel); // set the mapping (the mapping should be automatically removed when reply is destroyed)
Я делаю это для каждого запроса, который я делаю, я каждый раз подключаю QSignalMapper к своим слотам. Есть ли более элегантное решение, которое делает то же самое, возможно, используя один QSignalMapper?
Одним из простых способов сделать это было бы установить dataModel
как свойство в ответе.
Храните сетевой менеджер и другие объекты по значению, а не по указателю — указатель является дополнительной косвенной ссылкой и в большинстве случаев совершенно не нужен.
Ниже приведен полный пример C ++ 11, который работает как в Qt 4, так и в 5.
// https://github.com/KubaO/stackoverflown/tree/master/questions/netreply-property-38775573
#include <QtNetwork>
#include <QStringListModel> // needed for Qt 4
using DataModel = QStringListModel;
const char kDataModel[] = "dataModel";
class Worker : public QObject {
Q_OBJECT
QNetworkAccessManager m_manager;
Q_SLOT void onFeedRetrieved(QNetworkReply* reply) {
auto dataModelObject = qvariant_cast<QObject*>(reply->property(kDataModel));
auto dataModel = qobject_cast<DataModel*>(dataModelObject);
qDebug() << dataModel;
emit got(reply);
}
public:
Worker(QObject * parent = nullptr) : QObject{parent} {
connect(&m_manager, SIGNAL(finished(QNetworkReply*)),
SLOT(onFeedRetrieved(QNetworkReply*)));
}
void newRequest(const QUrl & url, DataModel * dataModel) {
QNetworkRequest request{url};
auto reply = m_manager.get(request);
reply->setProperty(kDataModel, QVariant::fromValue((QObject*)dataModel));
}
Q_SIGNAL void got(QNetworkReply*);
};
int main(int argc, char ** argv) {
QCoreApplication app{argc, argv};
DataModel model;
Worker worker;
worker.newRequest(
QUrl{"http://stackoverflow.com/questions/38775573/best-way-to-use-qsignalmapper"},
&model);
QObject::connect(&worker, SIGNAL(got(QNetworkReply*)), &app, SLOT(quit()));
return app.exec();
}
#include "main.moc"
Вы можете использовать только QSignalMapper
если есть соотношение 1: 1 между dataModel
экземпляр и ответ. Если одна модель данных используется для нескольких ответов, она не будет работать.
Если вы действительно беспокоитесь о количестве выделений, то использование системы свойств требует немного больше ресурсов: установка первого свойства для объекта выполняет как минимум два выделения — внутренний класс и сегмент данных для QMap
, Так что это 2N распределения. Для сравнения, добавление сопоставления QSignalMapper
выполняет амортизированные записи журнала (N). Иначе, QSignalMapper
бесполезно.
В Qt 5 его использование будет антипаттерном, так как вы можете подключиться к std::bind
или лямбда. В любом случае, было бы намного лучше, если QSignalMapper
сопоставлены с QVariant
,
Добавление первого соединения к объекту также выделяет (другой) внутренний класс. Чтобы избежать этой потенциальной стоимости, вы должны избегать добавления соединений к объектам, которые вы часто создаете. Предпочтительнее подключить один раз к QNetworkManager::finished(QNetworkReply*)
сигнал, а не подключение к каждому QNetworkReply::finished()
, Увы, это сохранение исчезает, когда вы используете соединения с очередями: в данный момент они требуют дополнительного выделения для каждого аргумента, передаваемого в слот. Это только недостаток текущей реализации, а не архитектурное ограничение; он может быть удален в последующем незначительном выпуске Qt (если я или кто-то другой получу к нему доступ).
#include <QtNetwork>
#include <QStringListModel> // needed for Qt 4
using DataModel = QStringListModel;
class Worker : public QObject {
Q_OBJECT
QNetworkAccessManager m_manager;
QSignalMapper m_mapper;
// QObject::connect is not clever enough to know that QNetworkReply* is-a QObject*
Q_SLOT void map(QNetworkReply* reply) { m_mapper.map(reply); }
Q_SLOT void onFeedRetrieved(QObject * dataModelObject) {
auto dataModel = qobject_cast<DataModel*>(dataModelObject);
auto reply = qobject_cast<QNetworkReply*>(m_mapper.mapping(dataModelObject));
qDebug() << dataModel << reply;
emit got(reply);
}
public:
Worker(QObject * parent = nullptr) : QObject{parent} {
connect(&m_manager, SIGNAL(finished(QNetworkReply*)), SLOT(map(QNetworkReply*)));
connect(&m_mapper, SIGNAL(mapped(QObject*)), SLOT(onFeedRetrieved(QObject*)));
}
void newRequest(const QUrl & url, DataModel * dataModel) {
QNetworkRequest request{url};
auto reply = m_manager.get(request);
// Ensure a unique mapping
Q_ASSERT(m_mapper.mapping(dataModel) == nullptr);
m_mapper.setMapping(reply, dataModel);
}
Q_SIGNAL void got(QNetworkReply*);
};
int main(int argc, char ** argv) {
QCoreApplication app{argc, argv};
DataModel model;
Worker worker;
QObject::connect(&worker, SIGNAL(got(QNetworkReply*)), &app, SLOT(quit()));
worker.newRequest(
QUrl{"http://stackoverflow.com/questions/38775573/best-way-to-use-qsignalmapper"},
&model);
return app.exec();
}
#include "main.moc"
Этот ответ обеспечивает элегантное и общее решение: использовать sender()
в комбинации с qobject_cast
вместо QSignalMapper
,
Ваш код может выглядеть так:
connect(reply,SIGNAL(finished()), this, SLOT(onFeedRetrieved()));
А потом:
void Foo::onFeedRetrieved()
{
QNetworkReply *reply = qobject_cast<QNetworkReply>(sender());
if (reply) { //check if the cast worked
//check which QNetworkReply invoked the slot and do stuff here
}
}