Qt 的信号槽在解耦方面做的非常好,sender 和 receiver 不需要互相知道,但是在使用 QObject::connect() 建立信号槽连接的时候是必须要同时知道 sender 和 receiver 的,这在绝大多数时候都是非常好用的,但是如果只想发送通知后,对此通知感兴趣的监听者就能自动的收到通知,同时还不用 QObject::connect() 建立信号槽连接,就可以使用下面介绍的 NotificationCenter
来实现。此外,NotificationCenter 内部已经处理好了跨线程通讯,不需要再考虑不同线程间函数调用的头疼问题。
NotificationCenter 的使用:
通知的监听器,不需要使用继承,只需要实现函数 notified
,当有通知的时候,notified
函数会被自动调用
1
| Q_INVOKABLE void notified(int notificationId, const QByteArray &data);
|
监听某个通知,通知的标志是一个整数,可以根据业务随意定义
1
| Singleton<NotificationCenter>::getInstance().addObserver(1, &foo);
|
发送通知, 只需要和 NotificationCenter 交互就能发送和接收通知
1 2
| Singleton<NotificationCenter>::getInstance().notify(1, QString("Two").toUtf8()); NOTIFY_CROSS_THREAD(2, QString("Two").toUtf8());
|
删除监听器,不再需要的时候,从 NotificationCenter 删除监听器,以免造成野指针异常
1 2
| Singleton<NotificationCenter>::getInstance().removeObserver(&foo); Singleton<NotificationCenter>::getInstance().removeObserver(1, &foo);
|
NotificationCenter 什么时候使用? 以 QTcpSocket 通讯为例:
使用信号槽和 NotificationCenter 都可以完成任务,但是 NotificationCenter 也是个不错的选择
- 如果每一种消息都对应一个信号,则一般都会对应一个槽函数(对创建太多函数总有莫名的恐惧感)
- 消息的种类会很多,就会需要太多的信号槽
- 需要使用 connect() 給信号槽对应的创建连接
- 如果要增加新的消息类型时,则要创建它的槽函数和建立连接
- 也许你会说,所有的消息都用同一个信号和槽函数,因为可以在发射时传入消息的种类,在槽函数里也是能区别消息的。是的,这确实是一个不错的方法,这种做法和我们的 NotificationCenter 其实就差不多一样了,但是有点区别的是它会把所有的消息都发送到槽函数里,不需要的也会发送,例如有 100 种消息,即使只对其中 2 种消息感兴趣,但是它也会收到另外的 98 种消息。 而使用 NotificationCenter 时只会收到你感兴趣的 2 种消息。
- 还有一点,NotificationCenter 中通知的监听器注册只需要关心通知的 ID,这个 ID 是根据业务需求定义的一个整数,不需要知道通知的发送者,但是建立信号槽连接时 sender 和 receiver 都需要知道,这也是相对方便的一点。
文中的 监听器
,观察者
指的是同一个意思;消息
,通知
也是同一个意思。
文件说明
- NotificationCenter 实现的文件包含
- NotificationCenter.h
- NotificationCenter.cpp
- Singleton.h
- 以下几个文件是用于测试使用的
- main.cpp
- FooObserver.h and FooObserver.cpp
- BarObserver.h and BarObserver.cpp
- NotifyThread.h and NotifyThread.cpp
NotificationCenter.h and NotificationCenter.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| #ifndef NOTIFICATIONCENTER_H #define NOTIFICATIONCENTER_H
#include <QObject> #include <QSet> #include <QHash> #include <QMetaObject>
#include "Singleton.h"
#define NOTIFY_CROSS_THREAD(notificationId, data) QMetaObject::invokeMethod(&Singleton<NotificationCenter>::getInstance(), \ "notify", Q_ARG(int, notificationId), Q_ARG(QByteArray, data));
class NotificationCenter : public QObject { Q_OBJECT SINGLETON(NotificationCenter) public: /** * @brief 給传入的 notificationId 添加观察者 observer * * @param notificationId 通知的 id * @param observer 观察者 */ Q_INVOKABLE void addObserver(int notificationId, QObject *observer);
Q_INVOKABLE void removeObserver(int notificationId, QObject *observer);
Q_INVOKABLE void removeObserver(QObject *observer);
Q_INVOKABLE void notify(int notificationId, const QByteArray &data);
private: QHash<int, QSet<QObject*> > observers; };
#endif
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include "NotificationCenter.h" #include <QList>
NotificationCenter::NotificationCenter() {}
NotificationCenter::~NotificationCenter() {}
void NotificationCenter::addObserver(int notificationId, QObject *observer) { if (NULL == observer) { return; }
QSet<QObject*> &obs = observers[notificationId]; obs.insert(observer); }
void NotificationCenter::removeObserver(int notificationId, QObject *observer) { QSet<QObject*> &obs = observers[notificationId]; obs.remove(observer); }
void NotificationCenter::removeObserver(QObject *observer) { QList<int> notificationIds = observers.keys();
foreach (int notificationId, notificationIds) { removeObserver(notificationId, observer); } }
void NotificationCenter::notify(int notificationId, const QByteArray &data) { QSet<QObject*> obs = observers[notificationId];
foreach(QObject *observer, obs) { QMetaObject::invokeMethod(observer, "notified", Q_ARG(int, notificationId), Q_ARG(QByteArray, data)); } }
|
Singleton.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| #ifndef SINGLETON_H #define SINGLETON_H
#include <QMutex> #include <QScopedPointer>
template <typename T> class Singleton { public: static T& getInstance();
Singleton(const Singleton &other); Singleton<T>& operator=(const Singleton &other);
private: static QMutex mutex; static QScopedPointer<T> instance; };
template <typename T> QMutex Singleton<T>::mutex; template <typename T> QScopedPointer<T> Singleton<T>::instance;
template <typename T> T& Singleton<T>::getInstance() { if (instance.isNull()) { mutex.lock(); if (instance.isNull()) { instance.reset(new T()); } mutex.unlock(); }
return *instance.data(); }
#define SINGLETON(Class) \ private: \ Class(); \ ~Class(); \ Class(const Class &other); \ Class& operator=(const Class &other); \ friend class Singleton<Class>; \ friend struct QScopedPointerDeleter<Class>;
#endif
|
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include <QApplication> #include <QDebug>
#include "NotificationCenter.h" #include "FooObserver.h" #include "BarObserver.h" #include "Message.h" #include "NotifyThread.h"
int main(int argc, char *argv[]) { QApplication app(argc, argv);
NotificationCenter &nc = Singleton<NotificationCenter>::getInstance(); FooObserver foo; BarObserver bar;
qDebug() << "---------------- foo and bar 都能收到通知 1 和 2 ----------------"; nc.addObserver(1, &foo); nc.addObserver(1, &bar); nc.addObserver(2, &bar); nc.notify(1, QString("One").toUtf8()); NOTIFY_CROSS_THREAD(2, QString("Two").toUtf8());
qDebug() << "---------------- bar 不再收到通知 1,但能收到通知 2 ----------------"; nc.removeObserver(1, &bar); nc.notify(1, QString("Three").toUtf8()); nc.notify(2, QString("Four").toUtf8());
qDebug() << "---------------- 序列化例子 ----------------"; nc.addObserver(3, &foo); Message msg(12345, "你是谁?你从哪里来?你要到哪里去?"); nc.notify(3, msg.toByteArray());
qDebug() << "---------------- 跨线程发送通知 ----------------"; nc.addObserver(4, &foo); NotifyThread thread; thread.start();
return app.exec(); }
|
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13
| ---------------- foo and bar 都能收到通知 1 和 2 ---------------- NotificationID: 1 - FooObserver: "One" NotificationID: 1 - BarObserver: "One" NotificationID: 2 - BarObserver: "Two" ---------------- bar 不再收到通知 1 ---------------- NotificationID: 1 - FooObserver: "Three" NotificationID: 2 - BarObserver: "Four" ---------------- 序列化例子 ---------------- NotificationID: 3 - FooObserver: "Type: 12345, Content: 你是谁?你从哪里来?你要到哪里去?" ---------------- 跨线程发送通知 ---------------- NotifyThread: NotifyThread(0x7fff519ed910) NotificationID: 4 - FooObserver: "870" - in thread: QThread(0x7fbfc0c04690) NotificationID: 4 - FooObserver: "345" - in thread: QThread(0x7fbfc0c04690)
|
FooObserver.h and FooObserver.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13
| #ifndef FOOOBSERVER_H #define FOOOBSERVER_H
#include <QObject>
class FooObserver : public QObject { Q_OBJECT public: explicit FooObserver(QObject *parent = 0); Q_INVOKABLE void notified(int notificationId, const QByteArray &data); };
#endif
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include "FooObserver.h" #include <QDebug> #include <QThread> #include "Message.h"
FooObserver::FooObserver(QObject *parent) : QObject(parent) {}
void FooObserver::notified(int notificationId, const QByteArray &data) { if (3 == notificationId) { qDebug() << "NotificationID: " << notificationId << " - " << "FooObserver: " << Message::fromByteArray(data).toString(); } else if (4 == notificationId) { qDebug() << "NotificationID: " << notificationId << " - " << "FooObserver: " << data << "- in thread: " << QThread::currentThread(); } else { qDebug() << "NotificationID: " << notificationId << " - " << "FooObserver: " << data; } }
|
BarObserver.h and BarObserver.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13
| #ifndef BAROBSERVER_H #define BAROBSERVER_H
#include <QObject>
class BarObserver : public QObject { Q_OBJECT public: explicit BarObserver(QObject *parent = 0); Q_INVOKABLE void notified(int notificationId, const QByteArray &data); };
#endif
|
1 2 3 4 5 6 7 8
| #include "BarObserver.h" #include <QDebug>
BarObserver::BarObserver(QObject *parent) : QObject(parent) {}
void BarObserver::notified(int notificationId, const QByteArray &data) { qDebug() << "NotificationID: " << notificationId << " - " << "BarObserver: " << data; }
|
NotifyThread.h and NotifyThread.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #ifndef NOTIFYTHREAD_H #define NOTIFYTHREAD_H #include <QThread>
class NotifyThread : public QThread { Q_OBJECT public: NotifyThread();
protected: void run() Q_DECL_OVERRIDE; };
#endif
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include "NotifyThread.h" #include "NotificationCenter.h" #include <QDebug> #include <QDateTime>
NotifyThread::NotifyThread() {}
void NotifyThread::run() { qDebug() << "NotifyThread: " << QThread::currentThread(); qsrand(QDateTime::currentDateTime().toMSecsSinceEpoch());
while (true) { NOTIFY_CROSS_THREAD(4, QString::number(qrand() % 900 + 100).toUtf8());
QThread::msleep(1000); } }
|