Bibi's DevLog πŸ€“πŸŽ

[iOS] Singleton 싱글톀 νŒ¨ν„΄μ΄λž€ 무엇이고, 단점과 λŒ€μ•ˆμ€ 무엇인가? λ³Έλ¬Έ

πŸ“±πŸŽ iOS

[iOS] Singleton 싱글톀 νŒ¨ν„΄μ΄λž€ 무엇이고, 단점과 λŒ€μ•ˆμ€ 무엇인가?

λΉ„λΉ„ bibi 2023. 2. 14. 23:04

What Is a Singleton and How To Create One In Swift

Are Singletons Bad

이 글은 μœ„μ˜ 두 λ¬Έμ„œμ— κΈ°λ°˜ν•˜μ—¬ 직접 μ •λ¦¬ν•œ κΈ€μž…λ‹ˆλ‹€.😷


싱글톀 νŒ¨ν„΄μ€ μ†Œν”„νŠΈμ›¨μ–΄ κ°œλ°œμ— 맀우 널리 μ‚¬μš©λ˜λŠ” λ””μžμΈ νŒ¨ν„΄μ΄μ§€λ§Œ, μ•ˆν‹°νŒ¨ν„΄μœΌλ‘œ κ³ λ €λ˜μ–΄μ§„λ‹€. κ·Έ 이유λ₯Ό μ‚΄νŽ΄λ³΄μž.

싱글톀 νŒ¨ν„΄μ΄λž€ 무엇인가

싱글톀 νŒ¨ν„΄μ€ 클래슀의 μΈμŠ€ν„΄μŠ€κ°€ 단 ν•˜λ‚˜λ§Œ μƒμ„±λ˜λ„λ‘ ν•˜λŠ” νŒ¨ν„΄μ΄λ‹€.

ν”„λ‘œκ·Έλž¨μ—μ„œ μ–΄λ–€ μ‹œμ μ—μ„œλ„ κ·Έ 클래슀의 μΈμŠ€ν„΄μŠ€λŠ” λ”± ν•˜λ‚˜λ§Œ μ‘΄μž¬ν•˜λ„λ‘ 보μž₯ν•˜λŠ” 것이 싱글톀 νŒ¨ν„΄μ˜ λͺ©μ μ΄λ‹€.

예λ₯Ό λ“€λ©΄, μ• ν”Œμ˜ ν”„λ ˆμž„μ›Œν¬μ—μ„œλŠ” μ•„λž˜μ™€ 같은 객체듀이 μ‹±κΈ€ν†€μœΌλ‘œ μ‚¬μš©λœλ‹€.

// Shared URL Session
let sharedURLSession = URLSession.shared

// Default File Manager
let defaultFileManager = FileManager.default

// Standard User Defaults
let standardUserDefaults = UserDefaults.standard

// Default Payment Queue
let defaultPaymentQueue = SKPaymentQueue.default()

μ „μ—­ μ ‘κ·Ό

싱글톀 νŒ¨ν„΄μ€ μ „μ—­ μ ‘κ·Ό(global access)이 κ°€λŠ₯ν•˜λ‹€λŠ” νŠΉμ§•μ΄ 있으며, 이것은 싱글톀 νŒ¨ν„΄μ˜ λΆ€μž‘μš©μ΄κΈ°λ„ ν•˜λ‹€.

μ „μ—­ 접근은 β€œν”„λ‘œμ νŠΈμ˜ λͺ¨λ“  객체가 이 객체에 μ ‘κ·Όν•  수 μžˆμŒβ€μ„ μ˜λ―Έν•œλ‹€. 이것은 νŽΈλ¦¬ν•˜μ§€λ§Œ 그만큼의 λΆ€μž‘μš©μ΄ λ”°λ₯Έλ‹€.

싱글톀 νŒ¨ν„΄μ„ μ–΄λ–»κ²Œ λ§Œλ“œλŠ”κ°€

static ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•΄ λ§Œλ“€ 수 μžˆλ‹€.

싱글톀 λ§Œλ“€κΈ° - (1) static ν”„λ‘œνΌν‹°μ™€ private μƒμ„±μž

class NetworkManager {

    // MARK: - Properties

    static let shared = NetworkManager(baseURL: API.baseURL)

    // MARK: -

    let baseURL: URL

    // Initialization

    private init(baseURL: URL) {
        self.baseURL = baseURL
    }

}
  • μƒμ„±μžκ°€ private이닀.
    • 였직 클래슀 자기 μžμ‹ λ§Œμ΄ μžμ‹ μ˜ μΈμŠ€ν„΄μŠ€λ₯Ό 생성할 수 μžˆλ‹€.
  • shared λΌλŠ” μ΄λ¦„μ˜ static μƒμˆ˜ ν”„λ‘œνΌν‹°λ‘œ 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•œλ‹€.
    • 이 ν”„λ‘œνΌν‹°λ₯Ό 톡해 λ‹€λ₯Έ 객체듀이 싱글톀 객체에 μ ‘κ·Όν•  수 μžˆλ‹€.
  • static ν”„λ‘œνΌν‹°λŠ” 이미 lazy둜 λ™μž‘ν•˜λ―€λ‘œ, lazy ν‚€μ›Œλ“œκ°€ ν•„μš”μ—†λ‹€.

μ‚¬μš©ν•˜κΈ° (1)

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    print(NetworkManager.shared)
    return true
}

싱글톀객체.shared 와 같이 μ‚¬μš©ν•˜λ©΄ λœλ‹€.

싱글톀 λ§Œλ“€κΈ° - (2) private static ν΄λ‘œμ €μ™€ class λ©”μ„œλ“œ

class NetworkManager {

    // MARK: - Properties

    private static var sharedNetworkManager: NetworkManager = {
        let networkManager = NetworkManager(baseURL: API.baseURL)

        // Configuration
        // ...

        return networkManager
    }()

    // MARK: -

    let baseURL: URL

    // Initialization

    private init(baseURL: URL) {
        self.baseURL = baseURL
    }

    // MARK: - Accessors

    class func shared() -> NetworkManager {
        return sharedNetworkManager
    }

}

μœ„μ˜ 방법보닀 μ•½κ°„ 더 λ³΅μž‘ν•œ 방식이닀.

  • private static propertyλ₯Ό ν΄λ‘œμ €λ‘œ 생성
    • 싱글톀 객체의 μ΄ˆκΈ°ν™”λ₯Ό ν΄λ‘œμ € λ‚΄μ—μ„œ μ§„ν–‰ν•œλ‹€. 이λ₯Ό 톡해 싱글톀 객체에 λŒ€ν•œ 더 λ³΅μž‘ν•œ μ΄ˆκΈ°ν™”μ™€ ꡬ성을 ν•  수 μžˆλ‹€.
  • shared() 클래슀 λ©”μ„œλ“œλ₯Ό 톡해 싱글톀 객체에 μ ‘κ·Όν•  수 μžˆλ‹€.

μ‚¬μš©ν•˜κΈ° (2)

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    print(NetworkManager.shared())
    return true
}

싱글톀객체.shared() 와 같이 μ‚¬μš©ν•˜λ©΄ λœλ‹€.

싱글톀 νŒ¨ν„΄μ˜ 단점 - νŽΈμ˜μ„± λ•Œλ¬Έμ— 투λͺ…성을 ν¬μƒν•˜λ‹€

싱글톀을 μ‚¬μš©ν•˜λ©΄ λ°˜λ“œμ‹œ νŽΈμ˜μ„± λ•Œλ¬Έμ— 투λͺ…성을 ν¬μƒν•˜κ²Œ λœλ‹€.

μ†Œν”„νŠΈμ›¨μ–΄μ—μ„œ β€œνŽΈμ˜μ„±β€μ€ μš°μ„ μˆœμœ„ μƒμœ„μ— μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”λ‹€. 즉 νŽΈμ˜μ„±λ³΄λ‹€ 투λͺ…성이 더 μ€‘μš”ν•˜λ‹€.

예λ₯Ό λ“€μ–΄ ν”„λ‘œμ νŠΈ λ‚΄μ—μ„œ μ‚¬μš©μž 관리λ₯Ό ν•΄μ•Ό ν•œλ‹€κ³  생각해 보자.
즉 μ‚¬μš©μž 객체가 μƒμ„±λ˜κ³  κ΄€λ¦¬λ˜μ–΄μ•Ό ν•œλ‹€.
이 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ 싱글톀 νŒ¨ν„΄μ„ μ‚¬μš©ν•œλ‹€λ©΄ - ν•œ λ²ˆμ— 였직 ν•œ λͺ…μ˜ μ‚¬μš©μžλ§Œμ΄ λ‘œκ·ΈμΈν•  수 μžˆλ‹€.

싱글톀 νŒ¨ν„΄μ„ μ‚¬μš©ν•˜λŠ” 것이 더 λ‚˜μ„ λ•Œλ„ μžˆμ§€λ§Œ, 단점이 μž₯점보닀 훨씬 λ§Žλ‹€. μ²˜μŒμ—λŠ” 단점이 맀우 λ―Έλ¬˜ν•˜κΈ° λ•Œλ¬Έμ—, λ§Žμ€ κ°œλ°œμžλ“€μ΄ 싱글톀 νŒ¨ν„΄μ„ 잘λͺ» μ‚¬μš©ν•˜κ²Œ λœλ‹€.

싱글톀 νŒ¨ν„΄μ˜ κ°€μž₯ μ€‘μš”ν•œ 단점은 νŽΈμ˜μ„± λ•Œλ¬Έμ— 투λͺ…성을 ν¬μƒν•œλ‹€λŠ” 것이닀. μ§€λ‚œ μ˜ˆμ œμ™€ 같이, μ‹œκ°„μ΄ μ§€λ‚ μˆ˜λ‘ 싱글톀 객체에 μ ‘κ·Όν•˜λŠ” 객체λ₯Ό 좔적할 수 μ—†κ²Œ λœλ‹€. 그리고, 더 μ€‘μš”ν•œ 것은, 싱글톀 객체의 ν”„λ‘œνΌν‹°λ₯Ό μˆ˜μ •ν•˜λŠ” 객체듀도 좔적할 수 μ—†κ²Œ λœλ‹€.

ν”„λ‘œμ νŠΈμ—μ„œ 싱글톀 객체λ₯Ό μ‚¬μš©ν•˜λ©΄μ„œ 당신은 κΈ°μˆ λΆ€μ±„(technical debt)λ₯Ό λ§Œλ“€κΈ° μ‹œμž‘ν•˜κ²Œ λœλ‹€. 싱글톀은 μ ‘κ·Όν•˜κΈ° λ„ˆλ¬΄ 쉽기 λ•Œλ¬Έμ— λ°”μ΄λŸ¬μŠ€μ²˜λŸΌ νΌμ Έλ‚˜κ°€λŠ” κ²½ν–₯이 μžˆλ‹€. 싱글톀 객체가 μ–΄λ””μ—μ„œ 쓰이고 μžˆλŠ”μ§€ μΆ”μ ν•˜λŠ” 것은 μ–΄λ €μš°λ©°, 크고 λ³΅μž‘ν•œ ν”„λ‘œμ νŠΈμ—μ„œ 싱글톀을 μ œκ±°ν•˜λŠ” 것은 λ¦¬νŒ©ν† λ§ 지μ˜₯이 될 수 μžˆλ‹€. 싱글톀 νŒ¨ν„΄μ΄ ν•œ 번 ν”„λ‘œμ νŠΈμ— μ‚¬μš©λ˜κΈ° μ‹œμž‘ν•˜λ©΄, 더 λ§Žμ€ 싱글톀 없이 ν”„λ‘œμ νŠΈλ₯Ό λ‚˜μ•„κ°€κ²Œ ν•˜λŠ” 것이 μ–΄λ €μ›Œμ§„λ‹€.

μ‹±κΈ€ν†€μ˜ 또 λ‹€λ₯Έ 단점은 ν…ŒμŠ€νŠΈμ΄λ‹€. 싱글톀 객체에 μ˜μ‘΄ν•˜λŠ” κ°μ²΄λŠ” κ°•ν•œ κ²°ν•©μœΌλ‘œ 인해 λ‹¨μœ„ ν…ŒμŠ€νŠΈκ°€ μ–΄λ €μ›Œμ§„λ‹€.

싱글톀 νŒ¨ν„΄μ„ 쑰금 더 투λͺ…ν•˜κ²Œ μ‚¬μš©ν•˜λŠ” 법

싱글톀 객체λ₯Ό ν•„μš”λ‘œ ν•˜λŠ” κ°μ²΄μ—κ²Œ μ˜μ‘΄μ„± μ£Όμž… λ°©μ‹μœΌλ‘œ 싱글톀 객체λ₯Ό μ „λ‹¬ν•˜λ©΄ 쒀더 투λͺ…ν•˜κ²Œ μ‚¬μš©ν•  수 μžˆλ‹€.

클래슀의 μΈν„°νŽ˜μ΄μŠ€κ°€ μžμ‹ μ˜ μ˜μ‘΄μ„±μ„ μ„€λͺ…ν•˜κ²Œ 되기 λ•Œλ¬Έμ΄λ‹€. 즉, ν΄λž˜μŠ€κ°€ μžμ‹ μ„ μƒμ„±ν•˜κΈ° μœ„ν•΄ μ–΄λ–€ 객체듀을 ν•„μš”λ‘œ ν•˜λŠ”μ§€ λͺ…ν™•ν•˜κ²Œ 보여쀀닀. ν”„λ‘œμ νŠΈ κ΅¬μ‘°λŠ” μœ μ—°ν•˜κ³  λŠμŠ¨ν•˜κ²Œ κ²°ν•©λœ κ²ƒμ²˜λŸΌ 보일 수 μžˆμ§€λ§Œ, μ‹€μ œλ‘œλŠ” μ „ν˜€ 그렇지 μ•Šλ‹€.

싱글톀 νŒ¨ν„΄μ˜ λŒ€μ•ˆ - μ˜μ‘΄μ„± μ£Όμž… (dependency injection)

싱글톀을 λŒ€μ²΄ν•  수 μžˆλŠ” 것은 μ˜μ‘΄μ„± μ£Όμž…μ΄λ‹€.

μ˜μ‘΄μ„± μ£Όμž…μ˜ κ°€μž₯ μ€‘μš”ν•œ μž₯점은 투λͺ…μ„±(transparency) 이닀. μ΄λŠ” μ‹±κΈ€ν†€μ˜ μž₯점인 νŽΈμ˜μ„±λ³΄λ‹€ 더 λ‚˜μ€ μž₯점이닀.

예λ₯Ό λ“€μ–΄, μ–΄λ–€ 객체가 역할을 μˆ˜ν–‰ν•˜λŠ” 데에 user객체λ₯Ό ν•„μš”λ‘œ ν•œλ‹€λ©΄, κ·Έ userκ°μ²΄λŠ” μ˜μ‘΄μ„±μœΌλ‘œμ„œ μ£Όμž…λ˜μ–΄μ•Ό ν•œλ‹€. μ½”λ“œλ‘œ μž‘μ„±ν•˜μžλ©΄ μ•„λž˜μ™€ κ°™λ‹€.

class User {
    var firstName = ""
    var lastName = ""
}

class NetworkController {

    let user: User

    init(user: User) {
        self.user = user
    }

}

μ˜μ‘΄μ„± μ£Όμž… 방식은 μŠ€λ‹ˆνŽ«μ„ 톡해, λͺ¨λ“  κ°œλ°œμžλ“€μ΄ NetworkControllerλ₯Ό μ‚¬μš©ν•  λ•Œ μœ νš¨ν•œ User μΈμŠ€ν„΄μŠ€μ— μ˜μ‘΄ν•  κ²ƒμž„μ„ μ•Œ 수 μžˆλ‹€.

μ΄λŠ” 참쑰둜 객체λ₯Ό μ „λ‹¬ν•˜λŠ” 방법(passing an object by reference)μœΌλ‘œλ„ μ•Œλ €μ Έ μžˆλ‹€.

μ˜μ‘΄μ„± μ£Όμž…μ€ λ‹Ήμž₯은 덜 νŽΈλ¦¬ν• μ§€ λͺ°λΌλ„, μž₯기적으둜 큰 이읡이 λœλ‹€. μ½”λ“œ 기반의 λͺ…확성을 더해주기 λ•Œλ¬Έμ— μ–΄λ–€ 객체가 μ–΄λ–€ 객체에 μ˜μ‘΄ν•˜κ³  μžˆλŠ”μ§€λ₯Ό μ •ν™•ν•˜κ²Œ 보여쀀닀.

λ˜ν•œ μ˜μ‘΄μ„± μ£Όμž…μ΄ 덜 νŽΈλ¦¬ν•˜λ‹€λŠ” 점이 이점이 λ˜κΈ°λ„ ν•œλ‹€. 당신이 더 μ‹ μ€‘ν•˜κ²Œ κ²°μ •ν•˜λ„λ‘ λ§Œλ“€κΈ° λ•Œλ¬Έμ΄λ‹€. NetworkController 객체가 User 객체에 λŒ€ν•΄ 직접 μ ‘κ·Όν•˜λŠ” λŒ€μ‹ , 이름과 λΉ„λ°€λ²ˆν˜Έλ§Œ μ „λ‹¬ν•˜λŠ” 걸둜 μΆ©λΆ„ν•  μˆ˜λ„ μžˆμ§€ μ•Šμ€κ°€? 이처럼 μ˜μ‘΄μ„± μ£Όμž…μ€ νƒ€μž…μ˜ μš”κ΅¬μ‚¬ν•­μ„ μ •μ˜ν•˜κΈ° μœ„ν•œ 맀우 μœ μš©ν•œ 도ꡬ가 λœλ‹€.

κ²°λ‘ 

싱글톀 νŒ¨ν„΄ μžμ²΄μ— 본질적인 λ¬Έμ œκ°€ μžˆλŠ” 것은 μ•„λ‹ˆλ‹€. 싱글톀은 μ œλŒ€λ‘œ μ‚¬μš©λœλ‹€λ©΄ 본질적으둜 λ‚˜μ˜μ§€ μ•Šλ‹€.

ν•˜μ§€λ§Œ λŒ€λΆ€λΆ„μ˜ κ°œλ°œμžλ“€μ΄ β€œνŽΈλ¦¬ν•¨β€μ΄λΌλŠ” 잘λͺ»λœ 이유 λ•Œλ¬Έμ— 싱글톀 νŒ¨ν„΄μ„ μ‚¬μš©ν•˜λŠ” 것이 문제인 것이닀. 싱글톀 κ°μ²΄λŠ” μ•„λ¬΄λ°μ„œλ‚˜ λ§ˆμŒλŒ€λ‘œ μ ‘κ·Όν•˜λΌκ³  λ§Œλ“  객체가 μ•„λ‹ˆλ‹€.

싱글톀 νŒ¨ν„΄μ˜ λͺ©μ μ€ νŽΈλ¦¬ν•¨μ΄ μ•„λ‹ˆλΌ, μ–΄λ–€ μ‹œμ μ—λ„ 클래슀의 μΈμŠ€ν„΄μŠ€κ°€ 단 ν•œκ°œλ§Œ μ‘΄μž¬ν•˜λ„λ‘ 보μž₯ν•˜λŠ” 것이닀. 싱글톀을 μ‚¬μš©ν•  λ•ŒλŠ” 이 λͺ©μ μ„ λ°˜λ“œμ‹œ 염두에 두어야 ν•œλ‹€.

μ •λ¦¬ν•˜μžλ©΄, 싱글톀 νŒ¨ν„΄μ€ 맀우 맀우 μ œν•œμ μœΌλ‘œ μ‚¬μš©λ˜μ–΄μ•Ό ν•œλ‹€.

λ§Œμ•½ 당신이 싱글톀 객체λ₯Ό λ§Œλ“œλ €κ³  ν•œλ‹€λ©΄, μž μ‹œ λ©ˆμΆ°μ„œ λ‹€λ₯Έ 선택지가 μ—†λŠ”μ§€ 생각해봐야 ν•œλ‹€. 싱글톀을 λ§Œλ“œλŠ” μ΄μœ κ°€ νŽΈλ¦¬ν•¨ λ•Œλ¬Έμ΄λΌλ©΄, 싱글톀을 λ§Œλ“€μ–΄μ„œλŠ” μ•ˆλœλ‹€.