싱글톤에서 shared 를 사용하는 이유

김상우·2024년 4월 4일
0
post-custom-banner

싱글톤

싱글톤 (Singleton) 은 소프트웨어 디자인 패턴 중 하나로, 앱 전역에서 오직 하나의 인스턴스만 살아있는 클래스를 말한다.

Java, Kotlin 과 같이 프로그래밍 언어 차원에서 기본적으로 싱글톤 클래스를 생성할 수 있도록 제공해주는 경우도 있고, Swift 나 Python 처럼 기본 제공을 해주진 않지만 개발자가 싱글톤을 직접 구현하는 경우도 있다.

개발자들의 일반적인 센스에서, 싱글톤을 직접 구현할 때 shared 라는 이름의 객체를 생성한다.
다음은 일반적인 Swift 의 싱글톤 형태다.

// 임의의 과일을 생성하는 싱글톤 클래스.
final class FruitManager {

	// 이 싱글톤 인스턴스를 사용하기 위해서는 이 shared 객체를 사용한다.
    static let shared = FruitManager()

    private init() {}

    func getFruit() -> String {
        return "사과"
    }
}

// 싱글톤을 사용하는 쪽의 코드.
let someFruit = FruitManager.shared.getFruit()

shared 없는 싱글톤

아래처럼 shared 를 쓰지 않고, 클래스 내부에 static 메서드만 만들어 싱글톤을 구현할 수도 있다. 똑같이 앱 전역에서 클래스의 인스턴스는 오직 하나다.

// 임의의 어떤 수를 제공해주는 싱글톤 클래스.
final class FruitManager {

	// init 을 못하게 막아버림.
	@available(*, unavailable) private init() {}

	// static 메서드 선언.
    static func getFruit() -> String {
        return "사과"
    }
}

// 싱글톤을 사용하는 쪽의 코드. shared 만 없을 뿐, 사용성이 같다.
let someFruit = FruitManager.getFruit()

shared 를 사용하는 이유

그렇다면 왜 모든 예시들에서는 당연하다는듯이 shared 객체를 만들어 싱글톤을 구현하고 있을까. 그 이유는 아래와 같다.

  1. 프로토콜을 준수하기 위함.
  2. 테스트 코드 작성 용이.

1. shared 객체가 있어야만 프로토콜을 만족하는 싱글톤을 만들 수 있다.

// 목업을 위한 프로토콜.
protocol FruitManagerProtocol {

    func getFruit() -> String
}

// 프로토콜을 채택한 싱글톤 클래스 (1) shared 사용.
final class FruitManager: FruitManagerProtocol {

    static let shared = FruitManager()

    private init() {}

    func getFruit() -> String {
        return "사과"
    }
}

// 프로토콜을 채택한 싱글톤 클래스 (2) shared 사용 x.
final class FruitManager2: FruitManagerProtocol {

	@available(*, unavailable) private init() {}
    
    // static func 으로는 프로토콜을 준수할 수 없음. 에러.
    static func getFruit() -> String {
        return "사과"
    }
}

2. 프로토콜을 준수함으로써 목킹과 테스트 코드 작성에 용이하다.

import Foundation
import XCTest
@testable import Common

final class SomeTest: XCTestCase {

	// 목킹한 클래스.
    class MockFruitManager: FruitManagerProtocol {
        func getFruit() -> String {
            return "딸기"
        }
    }

    func testGetFruit() throws {
        let fruitManager = MockFruitManager()
        let result = fruitManager.getFruit() == "사과"

        XCTAssertEqual(result, false)
    }
}

FruitManager2 와 같이 shared 를 사용하지 않는 static 한 클래스는 어떻게 활용하면 좋을까. 프로토콜을 채택할 필요없는 순수한 유틸성 클래스에 활용하는 것이 좋다.

profile
안녕하세요, iOS 와 알고리즘에 대한 글을 씁니다.
post-custom-banner

0개의 댓글