QML의 싱글톤

QML에서 싱글톤은 engine 당 최대 한 번만 생성되는 객체입니다. 이 가이드에서는 싱글톤을 만드는 방법과 싱글톤을 사용하는 방법을 설명합니다. 또한 싱글톤 작업에 대한 몇 가지 모범 사례도 제공합니다.

QML에서 싱글톤은 어떻게 만들 수 있나요?

QML에서 싱글톤을 만드는 방법에는 두 가지가 있습니다. QML 파일에서 싱글톤을 정의하거나 C++에서 싱글톤을 등록할 수 있습니다.

QML에서 싱글톤 정의하기

QML에서 싱글톤을 정의하려면 먼저 파일 상단에

pragma Singleton

을 파일 맨 위에 추가해야 합니다. 한 단계가 더 있습니다: QML 모듈의 qmldir 파일에 항목을 추가해야 합니다.

qt_add_qml_module(CMake) 사용

CMake를 사용할 때, qmldir은 qt_add_qml_module에 의해 자동으로 생성됩니다. QML 파일을 싱글톤으로 바꾸려면 QT_QML_SINGLETON_TYPE 파일 속성을 설정해야 합니다:

set_source_files_properties(MySingleton.qml
    PROPERTIES QT_QML_SINGLETON_TYPE TRUE)

set_source_files_properties 에 한 번에 여러 파일을 전달할 수 있습니다:

set(plain_qml_files
    MyItem1.qml
    MyItem2.qml
    FancyButton.qml
)
set(qml_singletons
    MySingleton.qml
    MyOtherSingleton.qml
)
set_source_files_properties(${qml_singletons}
    PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
qt_add_qml_module(myapp
    URI MyModule
    QML_FILES ${plain_qml_files} ${qml_singletons}
)

참고: set_source_files_properties를 먼저 호출해야 합니다. qt_add_qml_module

qt_add_qml_module 없이

를 사용하지 않는 경우 qt_add_qml_module, 수동으로 qmldir 파일을 만들어야 합니다. 거기에서 싱글톤을 적절히 표시해야 합니다:

module MyModule
singleton MySingleton 1.0 MySingleton.qml
singleton MyOtherSingleton 1.0 MyOtherSingleton.qml

자세한 내용은 객체 유형 선언을 참조하세요.

C++에서 싱글톤 정의하기

C++에서 싱글톤을 QML에 노출하는 방법에는 여러 가지가 있습니다. 주요 차이점은 QML 엔진에서 필요할 때 클래스의 새 인스턴스를 생성해야 하는지, 아니면 기존 객체를 QML 프로그램에 노출해야 하는지에 따라 달라집니다.

싱글톤을 제공하기 위해 클래스 등록하기

싱글톤을 정의하는 가장 간단한 방법은 QObject 에서 파생되는 기본 구성 가능한 클래스를 만들고 QML_SINGLETONQML_ELEMENT 매크로로 표시하는 것입니다.

class MySingleton : public QObject
{
    Q_OBJECT
    QML_SINGLETON
    QML_ELEMENT
public:
    MySingleton(QObject *parent = nullptr) : QObject(parent) {
        // ...
    }
};

이렇게 하면 파일이 속한 QML 모듈에 MySingleton 이라는 이름으로 MySingleton 클래스가 등록됩니다. 다른 이름으로 노출하려면 QML_NAMED_ELEMENT 대신 사용할 수 있습니다.

클래스를 기본적으로 구성할 수 없거나 싱글톤이 인스턴스화되는 QQmlEngine 에 액세스해야 하는 경우 정적 생성 함수를 대신 사용할 수 있습니다. 이 함수는 MySingleton *create(QQmlEngine *, QJSEngine *) 서명을 가져야 하며, 여기서 MySingleton 은 등록되는 클래스의 유형입니다.

class MyNonDefaultConstructibleSingleton : public QObject
{
    Q_OBJECT
    QML_SINGLETON
    QML_NAMED_ELEMENT(MySingleton)
public:
    MyNonDefaultConstructibleSingleton(QJSValue id, QObject *parent = nullptr)
        : QObject(parent)
        , m_symbol(std::move(id))
    {}

    static MyNonDefaultConstructibleSingleton *create(QQmlEngine *qmlEngine, QJSEngine *)
    {
         return new MyNonDefaultConstructibleSingleton(qmlEngine->newSymbol(u"MySingleton"_s));
    }

private:
    QJSValue m_symbol;
};

참고: create 함수는 QJSEngineQQmlEngine 매개 변수를 모두 받습니다. 이는 역사적인 이유 때문입니다. 둘 다 실제로는 QQmlEngine 인 동일한 객체를 가리킵니다.

기존 객체를 싱글톤으로 노출하기

타사 API를 통해 생성되었을 수 있는 기존 객체가 있는 경우가 있습니다. 이 경우 하나의 싱글톤을 사용하여 해당 개체를 해당 속성으로 노출하는 것이 올바른 선택인 경우가 많습니다( 관련 데이터 함께 그룹화하기 참조). 그러나 그렇지 않은 경우(예: 노출해야 하는 객체가 하나만 있는 경우) 다음 접근 방식을 사용하여 MySingleton 유형의 인스턴스를 엔진에 노출합니다. 먼저 싱글톤을 foreign type 로 노출합니다:

struct SingletonForeign
{
    Q_GADGET
    QML_FOREIGN(MySingleton)
    QML_SINGLETON
    QML_NAMED_ELEMENT(MySingleton)
public:

    inline static MySingleton *s_singletonInstance = nullptr;

    static MySingleton *create(QQmlEngine *, QJSEngine *engine)
    {
        // The instance has to exist before it is used. We cannot replace it.
        Q_ASSERT(s_singletonInstance);

        // The engine has to have the same thread affinity as the singleton.
        Q_ASSERT(engine->thread() == s_singletonInstance->thread());

        // There can only be one engine accessing the singleton.
        if (s_engine)
            Q_ASSERT(engine == s_engine);
        else
            s_engine = engine;

        // Explicitly specify C++ ownership so that the engine doesn't delete
        // the instance.
        QJSEngine::setObjectOwnership(s_singletonInstance,
                                      QJSEngine::CppOwnership);
        return s_singletonInstance;
    }

private:
    inline static QJSEngine *s_engine = nullptr;
};

그런 다음 첫 번째 엔진을 시작하기 전에 SingletonForeign::s_singletonInstance 을 설정합니다.

SingletonForeign::s_singletonInstance = getSingletonInstance();
QQmlApplicationEngine engine;
engine.loadFromModule("MyModule", "Main");

참고: 이 경우 단순히 qmlRegisterSingletonInstance 을 사용하고 싶을 수 있습니다. 그러나 다음 섹션에 나열된 명령형 유형 등록의 함정에 주의하세요.

명령형 등록

Qt 5.15 이전에는 싱글톤을 포함한 모든 타입이 qmlRegisterType API를 통해 등록되었습니다. 특히 싱글톤은 qmlRegisterSingletonType 또는 qmlRegisterSingletonInstance 을 통해 등록되었습니다. 각 타입마다 모듈 이름을 반복해야 하고 클래스 선언과 등록을 강제 분리해야 하는 사소한 번거로움 외에도 이 접근 방식의 가장 큰 문제는 툴링 비친화적이라는 점입니다. 컴파일 시점에 모듈의 타입에 필요한 모든 정보를 정적으로 추출할 수 없다는 것이었습니다. 선언적 등록은 이 문제를 해결했습니다.

참고: 명령형 qmlRegisterType API의 사용 사례가 하나 더 남아 있습니다: QObject 유형이 아닌 싱글톤을 var 프로퍼티로 노출하는 방법입니다. the QJSValue based qmlRegisterSingletonType overload . 대안을 선호합니다: 해당 값을 (QObject) 기반 싱글톤의 속성으로 노출하여 유형 정보를 사용할 수 있도록 합니다.

싱글톤에 액세스하기

싱글톤은 QML과 C++ 모두에서 액세스할 수 있습니다. QML에서는 포함된 모듈을 임포트해야 합니다. 그런 다음 이름을 통해 싱글톤에 액세스할 수 있습니다. 자바스크립트 컨텍스트 내에서 프로퍼티를 읽고 쓰는 것은 일반 객체와 동일한 방식으로 수행됩니다:

import QtQuick
import MyModule

Item {
    x: MySingleton.posX
    Component.onCompleted: MySingleton.ready = true;
}

싱글톤 프로퍼티에 바인딩을 설정하는 것은 불가능하지만, 필요한 경우 Binding 요소를 사용하여 동일한 결과를 얻을 수 있습니다:

import QtQuick
import MyModule

Item {
    id: root
    Binding {
        target: MySingleton
        property: "posX"
        value: root.x
    }
}

참고: 싱글톤 프로퍼티에 바인딩을 설치할 때는 주의를 기울여야 합니다: 두 개 이상의 파일에 의해 수행되는 경우 결과가 정의되지 않습니다.

싱글톤 사용(하지 않음)에 대한 가이드라인

싱글톤을 사용하면 여러 위치에서 액세스해야 하는 데이터를 엔진에 노출할 수 있습니다. 요소 사이의 간격과 같이 전역적으로 공유되는 설정이나 여러 위치에 표시해야 하는 데이터 모델 등이 이에 해당합니다. 비슷한 사용 사례를 해결할 수 있는 컨텍스트 프로퍼티와 비교할 때, 컨텍스트 프로퍼티는 입력할 수 있고, 다음과 같은 도구에서 지원되며 QML Language Server와 같은 도구에서 지원되며 일반적으로 런타임에 더 빠릅니다.

모듈에 싱글톤을 너무 많이 등록하지 않는 것이 좋습니다: 싱글톤은 한 번 생성되면 엔진 자체가 파괴될 때까지 살아 있으며, 글로벌 상태의 일부이므로 공유 상태라는 단점이 있습니다. 따라서 애플리케이션에서 싱글톤의 양을 줄이려면 다음 기술을 사용하는 것이 좋습니다:

노출하려는 각 객체에 대해 하나의 싱글톤을 추가하면 보일러 플레이트가 상당히 추가됩니다. 대부분의 경우 노출하려는 데이터를 단일 싱글톤의 속성으로 함께 그룹화하는 것이 더 합리적입니다. 예를 들어 abstract item models, 하나는 로컬 도서용이고 다른 하나는 원격 소스용인 세 개의 를 노출해야 하는 전자책 리더를 만들고 싶다고 가정해 보겠습니다. 기존 객체를 세 번 노출하는 프로세스를 반복하는 대신 하나의 싱글톤을 만들어 기본 애플리케이션을 시작하기 전에 설정할 수 있습니다:

class GlobalState : QObject
{
    Q_OBJECT
    QML_ELEMENT
    QML_SINGLETON
    Q_PROPERTY(QAbstractItemModel* localBooks MEMBER localBooks)
    Q_PROPERTY(QAbstractItemModel* digitalStoreFront MEMBER digitalStoreFront)
    Q_PROPERTY(QAbstractItemModel* publicLibrary MEMBER publicLibrary)
public:
    QAbstractItemModel* localBooks;
    QAbstractItemModel* digitalStoreFront;
    QAbstractItemModel* publicLibrary
};

int main() {
    QQmlApplicationEngine engine;
    auto globalState = engine.singletonInstance<GlobalState *>("MyModule", "GlobalState");
    globalState->localBooks = getLocalBooks();
    globalState->digitalStoreFront = setupLoalStoreFront();
    globalState->publicLibrary = accessPublicLibrary();
    engine.loadFromModule("MyModule", "Main");
}

개체 인스턴스 사용

지난 섹션에서는 세 개의 모델을 싱글톤의 멤버로 노출하는 예제를 살펴봤습니다. 이는 모델을 여러 곳에서 사용해야 하거나 우리가 제어할 수 없는 외부 API에서 모델을 제공할 때 유용할 수 있습니다. 그러나 모델이 한 곳에서만 필요한 경우에는 모델을 인스턴스화 가능한 유형으로 사용하는 것이 더 합리적일 수 있습니다. 이전 예제로 돌아와서 인스턴스화 가능한 RemoteBookModel 클래스를 추가한 다음 책 브라우저 QML 파일 내에 인스턴스화할 수 있습니다:

// remotebookmodel.h
class RemoteBookModel : public QAbstractItemModel
{
    Q_OBJECT
    QML_ELEMENT
    Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
    // ...
};

// bookbrowser.qml
Row {
    ListView {
        model: RemoteBookModel { url: "www.public-lib.example"}
    }
    ListView {
        model: RemoteBookModel { url: "www.store-front.example"}
    }
}

초기 상태 전달하기

싱글톤을 사용하여 QML에 상태를 전달할 수 있지만, 상태가 애플리케이션의 초기 설정에만 필요한 경우에는 낭비입니다. 이 경우 종종 QQmlApplicationEngine::setInitialProperties 을 사용할 수 있습니다. 예를 들어 해당 명령줄 플래그가 설정되어 있는 경우 Window::visibility 을 전체 화면으로 설정할 수 있습니다:

QQmlApplicationEngine engine;
if (parser.isSet(fullScreenOption)) {
    // assumes root item is ApplicationWindow
    engine.setInitialProperties(
        { "visibility", QVariant::fromValue(QWindow::FullScreen)}
    );
}
engine.loadFromModule("MyModule, "Main");

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.