 
                스위프트로 작성된 싱글턴
싱글턴은 같은 종류의 객체가 하나만 존재하도록 하고 다른 코드의 해당 객체에 대한 단일 접근 지점을 제공하는 생성 디자인 패턴입니다.
싱글턴은 전역 변수들과 거의 같은 장단점을 가지고 있습니다: 매우 편리하나 코드의 모듈성을 깨뜨립니다.
싱글턴에 의존하는 클래스를 다른 콘텍스트에서 사용하려면 싱글턴도 다른 콘텍스트로 전달해야 합니다. 대부분의 경우 이 제한 사항은 유닛 테스트를 생성하는 동안 발생합니다.
복잡도:
인기도:
사용 사례들: 많은 개발자는 싱클턴을 안티패턴으로 간주합니다. 그래서 스위프트 언어에서의 사용이 감소하고 있습니다.
식별: 싱글턴은 같은 캐싱 된 객체를 반환하는 정적 생성 메서드로 식별될 수 있습니다.
개념적인 예시
이 예시는 싱글턴 패턴의 구조를 보여주고 다음 질문에 중점을 둡니다:
- 패턴은 어떤 클래스들로 구성되어 있나요?
- 이 클래스들은 어떤 역할을 하나요?
- 패턴의 요소들은 어떻게 서로 연관되어 있나요?
이 패턴의 구조를 배우면 실제 스위프트 언어의 사용 사례를 기반으로 하는 다음 예시를 더욱 쉽게 이해할 수 있을 것입니다.
Example.swift: 개념적인 예시
import XCTest
/// The Singleton class defines the `shared` field that lets clients access the
/// unique singleton instance.
class Singleton {
    /// The static field that controls the access to the singleton instance.
    ///
    /// This implementation let you extend the Singleton class while keeping
    /// just one instance of each subclass around.
    static var shared: Singleton = {
        let instance = Singleton()
        // ... configure the instance
        // ...
        return instance
    }()
    /// The Singleton's initializer should always be private to prevent direct
    /// construction calls with the `new` operator.
    private init() {}
    /// Finally, any singleton should define some business logic, which can be
    /// executed on its instance.
    func someBusinessLogic() -> String {
        // ...
        return "Result of the 'someBusinessLogic' call"
    }
}
/// Singletons should not be cloneable.
extension Singleton: NSCopying {
    func copy(with zone: NSZone? = nil) -> Any {
        return self
    }
}
/// The client code.
class Client {
    // ...
    static func someClientCode() {
        let instance1 = Singleton.shared
        let instance2 = Singleton.shared
        if (instance1 === instance2) {
            print("Singleton works, both variables contain the same instance.")
        } else {
            print("Singleton failed, variables contain different instances.")
        }
    }
    // ...
}
/// Let's see how it all works together.
class SingletonConceptual: XCTestCase {
    func testSingletonConceptual() {
        Client.someClientCode()
    }
}
Output.txt: 실행 결과
Singleton works, both variables contain the same instance.
실제 사례 예시
Example.swift: 실제 사례 예시
import XCTest
/// Singleton Design Pattern
///
/// Intent: Ensure that class has a single instance, and provide a global point
/// of access to it.
class SingletonRealWorld: XCTestCase {
    func testSingletonRealWorld() {
        /// There are two view controllers.
        ///
        /// MessagesListVC displays a list of last messages from a user's chats.
        /// ChatVC displays a chat with a friend.
        ///
        /// FriendsChatService fetches messages from a server and provides all
        /// subscribers (view controllers in our example) with new and removed
        /// messages.
        ///
        /// FriendsChatService is used by both view controllers. It can be
        /// implemented as an instance of a class as well as a global variable.
        ///
        /// In this example, it is important to have only one instance that
        /// performs resource-intensive work.
        let listVC = MessagesListVC()
        let chatVC = ChatVC()
        listVC.startReceiveMessages()
        chatVC.startReceiveMessages()
        /// ... add view controllers to the navigation stack ...
    }
}
class BaseVC: UIViewController, MessageSubscriber {
    func accept(new messages: [Message]) {
        /// Handles new messages in the base class
    }
    func accept(removed messages: [Message]) {
        /// Hanldes removed messages in the base class
    }
    func startReceiveMessages() {
        /// The singleton can be injected as a dependency. However, from an
        /// informational perspective, this example calls FriendsChatService
        /// directly to illustrate the intent of the pattern, which is: "...to
        /// provide the global point of access to the instance..."
        FriendsChatService.shared.add(subscriber: self)
    }
}
class MessagesListVC: BaseVC {
    override func accept(new messages: [Message]) {
        print("MessagesListVC accepted 'new messages'")
        /// Handles new messages in the child class
    }
    override func accept(removed messages: [Message]) {
        print("MessagesListVC accepted 'removed messages'")
        /// Handles removed messages in the child class
    }
    override func startReceiveMessages() {
        print("MessagesListVC starts receive messages")
        super.startReceiveMessages()
    }
}
class ChatVC: BaseVC {
    override func accept(new messages: [Message]) {
        print("ChatVC accepted 'new messages'")
        /// Handles new messages in the child class
    }
    override func accept(removed messages: [Message]) {
        print("ChatVC accepted 'removed messages'")
        /// Handles removed messages in the child class
    }
    override func startReceiveMessages() {
        print("ChatVC starts receive messages")
        super.startReceiveMessages()
    }
}
/// Protocol for call-back events
protocol MessageSubscriber {
    func accept(new messages: [Message])
    func accept(removed messages: [Message])
}
/// Protocol for communication with a message service
protocol MessageService {
    func add(subscriber: MessageSubscriber)
}
/// Message domain model
struct Message {
    let id: Int
    let text: String
}
class FriendsChatService: MessageService {
    static let shared = FriendsChatService()
    private var subscribers = [MessageSubscriber]()
    func add(subscriber: MessageSubscriber) {
        /// In this example, fetching starts again by adding a new subscriber
        subscribers.append(subscriber)
        /// Please note, the first subscriber will receive messages again when
        /// the second subscriber is added
        startFetching()
    }
    func startFetching() {
        /// Set up the network stack, establish a connection...
        /// ...and retrieve data from a server
        let newMessages = [Message(id: 0, text: "Text0"),
                           Message(id: 5, text: "Text5"),
                           Message(id: 10, text: "Text10")]
        let removedMessages = [Message(id: 1, text: "Text0")]
        /// Send updated data to subscribers
        receivedNew(messages: newMessages)
        receivedRemoved(messages: removedMessages)
    }
}
private extension FriendsChatService {
    func receivedNew(messages: [Message]) {
        subscribers.forEach { item in
            item.accept(new: messages)
        }
    }
    func receivedRemoved(messages: [Message]) {
        subscribers.forEach { item in
            item.accept(removed: messages)
        }
    }
}
Output.txt: 실행 결과
MessagesListVC starts receive messages
MessagesListVC accepted 'new messages'
MessagesListVC accepted 'removed messages'
======== At this point, the second subscriber is added ======
ChatVC starts receive messages
MessagesListVC accepted 'new messages'
ChatVC accepted 'new messages'
MessagesListVC accepted 'removed messages'
ChatVC accepted 'removed messages'