본문 바로가기

Swift

Singleton Pattern

728x90

주로 네트워크 관련 코드 또는 외부 라이브러리 코드를 보면 shared, standard, defaults로 프로퍼티 또는 메서드에 접근하고 있는 코드를 볼 수 있다. 이런 코드들은 싱글톤 패턴을 적용해 만들었기 때문인데, 싱글톤 패턴이 무엇인지 알아보자. 

 

Singleton

싱글 .. 벙글 .. 이 아니고 싱글톤, 이름에서 알 수 있는 것처럼 어떤 것을 '하나'로 관리/유지하고 있다는 것을 알 수 있다.

 

싱글톤에 대한 정의를 찾아보면 아래와 같다.

The singleton pattern is a software design pattern that restricts the instantiation of a class to one “single” instance.

즉, 한 개의 클래스로 만드는 객체는 단 하나여야 한다.는 규칙을 갖고 있는 디자인 패턴이다. 

 

.. 무슨 말이냐면,

프로젝트(= 프로그램)에 단 하나의 전역 객체를 만들어 놓고 외부에서 이 하나의 객체에만 접근할 수 있도록 하는 것이다.

= 특정 용도로 객체를 하나만 생성해서 공용으로 사용하고 싶을 때 사용하는 디자인 유형

 

예시 코드를 통해서 알아보자면, 아래와 같은 코드를 본 적이 있을 것이다. 

URLSession.shared

 

서버 통신을 할 때, iOS에서는 URLSession 객체를 사용하는데 이미 만들어져 있는 shared 객체에 접근을 해서 메서드를 수행할 수 있다.

 

UserDefaults.standard

 

UserDefaults에서 기본적으로 공유하고 있는 standard 저장소에 접근해서 읽고-쓸 수 있는 standard에 접근을 해서 메서드를 수행할 수 있다.

 

이런 코드들 모두 싱글턴 패턴을 기반으로 만들어졌다고 할 수 있다.

더보기

iOS에서는 언제 싱글톤 패턴을 사용할까?

 

let screen = UIScreen.main
let userDefault = UserDefaults.standard
let application = UIApplication.shared
let fileManager = FileManager.default
let notification = NotificationCenter.default​

싱글톤 패턴을 구현하고 싶다면 아래와 같이 구현하면 된다.

class SingletonExample {
    static let shared = SingletonExample()
    private init { }
}

static 키워드를 사용해서 클래스 외부에서, 프로젝트에서 전역적으로 사용할 수 있도록 만들고

private init 을 통해서 다른 곳에서 마음대로 초기화 및 객체로 만들 수 없도록 설정한다. 

 

이렇게 만들면 해당 프로젝트에서는 SingletonExample에 대한 객체는 shared 하나만 존재한다고 할 수 있다. (물론 몇가지 특수한 상황, 예를 들면 멀티 쓰레드,에서는 반드시 그렇다고 보장할 수 없다.) 


싱글톤 패턴의 특징

장점

싱글톤 패턴을 사용하면 무엇이 좋을까?

  • 메모리를 단 한번만 사용할 수 있다.
    프로젝트에서 해당 객체는 하나만 존재한다. 즉, 한번만 만들어지기 때문에 메모리 관리 역시 보다 수월해진다. 한번 만들어둔 객체는 꾸준히 재사용할 수 있다.
  • 객체 접근 시간이 줄어든다.
    다시 메모리에 할당하고, 초기화 하는 과정이 줄어들기 때문에 한번 만들어두면 다시 접근할 때 매번 객체를 만드는 것보다 시간이 짧다.
  • 전역 범위에서 상태, 데이터 전달이 쉬워진다.
    어디서든 접근할 수 있기 때문에, 어떤 상태를 표시 및 데이터를 공유하기에 적합하다. 

 

단점

그러나, 역시 단점도 존재한다.

 

  • 테스트가 힘들다.
    싱글톤 패턴은 주로 위에서처럼 init을 private으로 설정하여 유일성을 보장하는 경우가 많다. 그런 측면에서 보면 좋지만, 테스트용 mock 객체를 만들기 어렵다.
  • 의존성을 만들어낸다.
    클래스 외부에서 어디든지, 쉽게 접근할 수 있기 때문에 프로젝트의 여러 곳에서 사용될 수 있다. 이렇게 남발하게 되면 나중에 문제가 생겼을 때 어디서 생긴 것인지 확인하기 어려워진다.
    -> 싱글톤 인스턴스가 너무 많은 일을 하거나 많은 데이터를 공유할 경우 다른 클래스의 인스턴스 간에 결합도가 높아져서 "개방-퍠쇄 원칙"을 위배하게 된다. 
  • 멀리 쓰레드 환경에서 위험할 수 있다.
    앞서 말한 특수한 경우 중 하나로 멀티 쓰레드 환경에서 싱글톤 객체가 2개 .. 생기는 문제가 발생할 수 있다. 또한 싱글톤 객체 내부의 데이터를 동기적으로 처리하지 않으면 여러 객체가 동시에 사용하다가 꼬여버릴 수 있다.

 


그렇다면, 실제로 어떻게 사용하는 것이 좋을까?

 

예를 들어서 User 정보를 저장하는 클래스를 만들어보자.

class UserInfo {
    var id: String?
    var password: String?
    var name: String?
}

 

그리고 이 유저 정보를 총 3개의 ViewController에서 접근하여 관리한다고 할 때,

// A ViewController
let userInfo = UserInfo()
userInfo.id = "So-Kyte"

// B ViewController
let userInfo = UserInfo()
userInfo.password = "123"

// C ViewController
let userInfo = UserInfo()
userInfo.name = "So-Kyte"

위와 같이 세 개의 ViewController에서 각각 UserInfo 객체를 만들어 저장을 하게 되면 각 인스턴스의 프로퍼티에만 저장이 될 것이다.

그러면 이는 1명의 유저를 관리하는 것이 아니라 3명의 유저를 관리하는 것처럼 동작될 것이다.

 

이럴 때, 이 클래스에 대한 인스턴스는 최초 생성될 때 한번만 생성해서 전역에 두고 이후로는 인스턴스에 접근하여 프로퍼티 또는 메서드를 사용하도록 하는 것이다. 이 방법이 바로 싱글톤 패턴이다. 

(= 한 인스턴스를 어떤 클래스에서든 접근 가능하도록 만드는 것)

 

 

위의 UserInfo 클래스에 대해서 싱글톤 패턴을 적용하면 아래와 같다.

class UserInfo {
    static let shared = UserInfo()

    var id: String?
    var password: String?
    var name: String?

    private init() { }
}

1. 먼저 전역으로 저장될 것이기 때문에 static을 사용해서 인스턴스를 저장할 프로퍼티를 하나 만든다.

2. 혹시라도 외부에서 init 함수를 호출해 인스턴스를 또 생성하는 것을 막기 위해 init() 함수 접근 제어자를 private으로 지정한다. 

 

그리고 나서 각 ViewController에서 각각 따로 인스턴스를 만들었던 것을 아래와 같이, shared를 통해 하나의 인스턴스로 접근할 수 있도록 한다.

// A ViewController
let userInfo = UserInfo.shared
userInfo.id = "So-Kyte"

// B ViewController
let userInfo = UserInfo.shared
userInfo.password = "123"


// C ViewController
let userInfo = UserInfo.shared
userInfo.name = "So-Kyte"

'Swift' 카테고리의 다른 글

Closure (클로저)  (0) 2022.08.10
First-Class Citizen(일급 객체)  (0) 2022.08.09
프로토콜  (0) 2022.08.02
프로퍼티 - 저장 프로퍼티  (0) 2022.07.27
some  (0) 2022.04.29