2016-08-04 31 views
1

我在Qt 4.8中使用QSignalMapper。現在我在做網絡請求象下面這樣:使用QSignalMapper的最佳方式

// 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?

回答

2

一個簡單的方法來做到這將是設置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:一個dataModel實例和答覆之間的一對一映射。如果一個數據模型用於多個回覆,它將不起作用。

如果你真的關心分配計數,那麼使用屬性系統有一點開銷:設置對象的第一個屬性至少執行兩個分配 - 一個內部類和一個數據段QMap。所以這是2N分配。相比之下,將映射添加到QSignalMapper將執行分期日誌(N)分配。否則,QSignalMapper是無用的。

在Qt 5中,使用它將會是一個反模式,因爲您可以連接到std::bind或lambda。無論如何,如果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" 
1

This answer提供了一個優雅和常見的解決方案:使用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 
    } 
} 
+0

我該如何檢查哪個QNetworkReply調用了插槽?我必須使用QObject :: setProperty嗎? –

+0

您可以將指向活動回覆的指針存儲在'QMap'或'QHash'中,並通過一個鍵檢索它們,就像'QSignalMapper'一樣。或者你可以在答覆上設置一個對象名稱並檢查它。 –