jay's devnote
  • README
  • 자료구조 & 알고리즘
    • 자료구조
    • 알고리즘
  • 디자인 패턴
    • 디자인 패턴 원칙, 요약, 분류
    • IS-A 와 HAS-A
    • 전략 패턴
    • 옵저버 패턴
    • 데코레이터 패턴
    • 팩토리 패턴
    • 싱글턴 패턴
    • 커맨드 패턴
    • 어댑터, 퍼사드 패턴
    • 템플릿 메소드 패턴
    • 반복자, 컴포지트 패턴
    • 상태 패턴
    • 프록시 패턴
    • 복합 패턴
  • WWDC
    • 2015, Building Better Apps with Value Types in Swift
    • 2015, Protocol-Oriented Programming in Swift
    • 2016, Understanding Swift Performance
    • 2016, Protocol and Value Oriented Programming in UIKit Apps
    • 2017, Engineering for Testability
    • 2018, High Performance Auto Layout
    • 2018, Testing Tips & Tricks
    • 2020, Advances in UICollectionView
    • 2020, Lists in UICollectionView
  • 패러다임
    • 객체지향 프로그래밍, SOLID 원칙
      • SRP, 단일 책임 원칙
      • OCP, 개방 폐쇄 원칙
      • LSP, 리스코프 치환 원칙
      • ISP, 인터페이스 분리 원칙
      • DIP, 의존성 역전 원칙
    • 만들면서 느껴보는 POP
    • Swift로 함수형 프로그래밍 시작하기
  • 아키텍쳐
    • ReactorKit
      • Pulse(EN)
      • Pulse(KR)
    • Coordinator Pattern
  • iOS
    • Safari로 웹뷰의 세션/쿠키 정보 확인하기
    • App Icon 동적으로 변경하기
    • WKDataDetectorTypes의 데이터 탐지
    • Xcode에서 메모리 누수 확인하기
    • 개발 인증서 관리하기
    • required init?(coder: NSCoder)
    • UIFontMetrics 와 UIFont.preferredFont
    • 제약조건을 줄여주는 UIStackView
    • UICollectionView.CellRegistration<Cell, Item>
  • Swift
    • Swift API Design Guidelines
    • 패턴 매칭
    • allSatisfy()
    • 생성자
    • 프로토콜의 동적 디스패치와 정적 디스패치
    • Swift 문법 정리
  • RxSwift
    • RxSwift 핸드북
    • Just, From, Of
    • withLatestFrom
  • SwiftUI
    • SwiftUI에서의 마크다운 문법
    • @State, @Binding
    • ObservableObject, @ObservedObject, @Published
    • @ObservedObject vs @StateObject
  • Git
    • Git gitignore
    • Github API Rate limit
    • GitKraken(깃크라켄) 활용하기
    • GitKraken으로 Git-flow 활용하기
  • Etc
    • Struct을 [String: Any]로 변환할 때, Encodable의 Extension을 사용 해야 하나요?
    • Podfile, Dependency Rule(SPM)
    • 맥으로 고정 IP 연결하는 방법
    • SwiftPlantUML으로 UML 다이어그램 쉽게 그리기
    • Playground 가 열리지 않는 오류 해결하기
    • CocoaPods 제거하기
  • Python
    • 파이썬과 스위프트 문법 비교
    • 파이썬과 스위프트 문법 요약
  • Firebase
    • Storage를 API처럼 사용해보기
    • RealTime Database를 API처럼 사용해보기
Powered by GitBook
On this page
  • 1. 싱글턴과 전역변수
  • 2. Thread-Safety
  • 3. 해결하기
  • 4. 실제로 구현해보기 1
  • 5. 실제로 구현해보기 2
  • 6. 문제점
  • Reference
  1. 디자인 패턴

싱글턴 패턴

Previous팩토리 패턴Next커맨드 패턴

Last updated 2 years ago

  • 클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역 접근을 제공한다.

1. 싱글턴과 전역변수

  • 싱글턴 패턴대신 그냥 전역변수를 사용하면 왜 안될까요 ? 전역변수에 객체를 대입하면 애플리케이션이 시작될 때 객체가 생성됩니다. 만약 그 객체가 자원을 많이 차지하고, 그 객체를 애플리케이션이 끝날 때까지 한번도 사용하지 않는다면? 자원낭비죠.

  • 싱글턴 패턴을 사용하면 lazy하게(필요할 때만 생성되도록) 사용할 수 있습니다.

  • 아래의 코드처럼 생성자를 private 으로 만들고, 타입 메소드를 통해 이미 1번 생성했다면 그대로 리턴하고 생성되지 않았다면 생성해서 리턴하는 것으로 유일한 객체를 사용할 수 있습니다.

  • 하지만 아래의 코드는 문제가 있다.

/*
위아래 같은 코드지만 swift의 lazy 키워드 사용여부가 다름
*/
class Singleton {
    private init() { }

    private static var uniqueInstance: Singleton?

    static func getInstance() -> Singleton {
	//lazy 키워드를 쓰지 않고 lazy하게 사용
	//Singleton 객체를 한번도 사용하지 않는다면 생성되지 않으므로 그만큼 메모리 절약
	if uniqueInstance == nil {
	    uniqueInstance = Singleton()
	}
	return uniqueInstance!
    }
}

class Singleton {
    private init() { }

    //lazy는 static 키워드로 사용이 불가
    //'lazy' cannot be used on an already-lazy global
    private lazy var uniqueInstance: Singleton = Singleton()

    //uniqueInstance가 타입 인스턴스가 아니기 때문에 여기서도 static 삭제
    //Instance member 'uniqueInstance' cannot be used on type 'Singleton'
    func getInstance() -> Singleton {
	return uniqueInstance
    }
}

2. Thread-Safety

If a property marked with the lazy modifier is accessed by multiple threads simultaneously and the property hasn’t yet been initialized, there’s no guarantee that the property will be initialized only once.

  • 공식 문서에서도 lazy한 속성에 대해서는 한번만 초기화되어야 된다는 보장이 없다는 글이 있다.

  • 즉, 아래의 그림과 같은 상태이다. 아직 객체가 초기화되기 전에, 여러 곳에서 접근해서 원래 의도와는 다르게 여러개의 객체가 생기는 경우이다.

3. 해결하기

  1. Synchronization(동기화)

    • getInstance()를 동기화시켜버리는 것이다. 즉, 초기화 될 때까지 강제로 기다리게 해버려서 다음에 접근할 때는 안전하게 하는 방법이다. objc 같은 경우는 @synchronized 키워드가 있지만 swift에는 그런 키워드가 없다. 따라서 DispatchQueue.global().sync {} 같은 것을 사용해야 할 것이다.

    • 다만, 메소드를 동기화하면 성능이 100배쯤 저하된다고 한다. 그건 어떻게 하지? 가장 간단한 해결책은 getInstance()의 속도가 그리 중요하지 않다면(큰 파일이 아니라면) 그냥 둔다.

  2. lazy하게 사용하지 않고 그냥 처음부터 만들어버린다.

    • 어찌보면 가장 간결한 해결책일 수 있다. 예전과는 다르게 하드웨어의 성능이 좋아져서 상대적으로 메모리가 넉넉하기 때문에, 아주 특정한 상황이 아니라면 괜찮을 것이다.

class Singleton {
    private init() { }
    
    static var shared = Singleton()
}

4. 실제로 구현해보기 1

  • 예시코드로 초콜릿을 만드는 초콜릿 보일러 코드가 있다.

  • 초콜릿 보일러는 순서대로 채우고, 끓이고, 다음단계로 보낸다.

  • 아래의 코드를 그대로 사용했더니, 아직 초콜릿이 끓고 있는 와중에 새로운 초콜릿이 들어가버리는 오류가 발생했습니다. 이 코드를 싱글턴 패턴으로 바꿔보자.

class ChocolateBoiler {
    private var empty: Bool
    private var boiled: Bool
    
    private static var uniqueInstance: ChocolateBoiler?

    static func getInstance() -> ChocolateBoiler {
        if uniqueInstance == nil {
            uniqueInstance = ChocolateBoiler()
        }
        return uniqueInstance!
    }
    
    private init() {
        self.empty = true
        self.boiled = false
    }
    
    //채우기
    //보일러에 우유와 초콜릿을 혼합한 재료를 넣음
    func fill() {
        if isEmpty() {
            empty = false
            boiled = false
        }
    }
    
    //끓이기
    //비어있고, 끓이기전이라면 끓이기
    func boil() {
        if !isEmpty() && !isBoiled() {
            boiled = true
        }
    }
    
    //물빼기
    //끓인 재료의 물을 빼서 다음단계로 넘기기
    func drain() {
        if !isEmpty() && isBoiled() {
            empty = true
        }
    }
    
    func isEmpty() -> Bool {
        return empty
    }
    
    func isBoiled() -> Bool {
        return boiled
    }
}

5. 실제로 구현해보기 2

  • 2번째 방법으로 문제를 해결했다.

class ChocolateBoiler {
    private var empty: Bool
    private var boiled: Bool
    
    static var shared = ChocolateBoiler()
    
    private init() {
        self.empty = true
        self.boiled = false
    }
    
    //...위와 같음 

6. 문제점

  • 그럼에도 불구하고 싱글턴 패턴은 느슨한 결합 원칙을 따르지는 않습니다. 싱글턴을 사용하다 수정하게되면 그에 따르는 클래스들도 고쳐야 하는 경우가 많기 때문이죠.

  • 하나는 클래스는 하나의 책임을 따른다(SRP) 원칙 또한 따르지 않죠. 싱글턴은 자신의 인스턴스를 관리하는 일 외에도 원래 그 싱글턴을 사용하고자 하는 목적의 코드가 들어갈 거에요. 최소 2가지의 일을 하게 되는 것이죠. 그러나, 많은 개발자가 이미 싱글턴에 익숙하고 특정한 목적에 있어서 싱글턴을 사용하면 앱을 더욱 간단하게 만들 수 있기 때문에 사용합니다.

Reference

https://www.hanbit.co.kr/store/books/look.php?p_code=B6113501223