Swift

[Swift] Singleton과 Thread-Safe

여러 개의 뷰 컨트롤러가 유저의 아이디와 닉네임을 사용해야하는 상황이 있었다.

 

각 뷰 컨트롤러 클래스에 유저의 아이디와 닉네임을 저장하는 프로퍼티를 정의하여

초기화 시 그 값들을 넘겨주는 방식이었는데,

 

그렇게 하지않고 유저 정보를 저장하는 공유 객체를 만들어서 필요할때마다

참조하면 편할 것 같다는 생각이 들어 싱글톤 디자인 패턴을 적용하려고 했다.

 

로그인이 완료된 시점에 싱글톤 객체를 생성하고 로그아웃 시 객체 메모리를 해제하는 방법을 찾다가

우연히 Thread-Safe에 대해 공부하게 된 것을 정리한다.


보통 Swift에서 싱글톤 패턴 구현 방법을 찾으면 아래의 방법이 사용된다.

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

클래스 내부에서만 인스턴스 생성이 가능하고 한번 생성된 공유 객체는 변하지 않는다.

이런 특징 덕분에 이런 방법의 싱글톤은 Thread-Safe하다고 얘기한다.

Thread-Safe란?
멀티스레드 환경에서 어떤 함수나 객체가 여러 스레드에 의해 동시에 접근되더라도
문제없이 각 스레드에서 원하는 결과가 도출된다는 것을 의미한다. 

 

주로 싱글톤 또는 전역 변수를 사용할때 고려하는데, 만약 어떤 객체가 여러 스레드에 동시에 접근되어

초기화 되거나 값이 변경된다면 각 스레드에서 의도치 않은 결과가 나타날 수 있다.

 

lazy 키워드를 사용해 지연 저장 프로퍼티를 사용할때 관련된 문제를 겪을 수 있으며,

애플의 공식 Swift 언어 가이드에도 설명이 나와있다.

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.

지연 저장 프로퍼티가 아직 초기화되지 않았고 여러 스레드에서 동시에 그 프로퍼티에 접근한다면

여러번 초기화될 수 있어 Thread-Safe하지 않다. 

 

다시 싱글톤으로 돌아가서 아래 코드는 Thread-Safe 하다고 얘기할 수 있을까??

class Singleton {

    private static var privateShared : Singleton?

    class func shared() -> Singleton {
        guard let uwShared = privateShared else {
            privateShared = Singleton()
            return privateShared!
        }
        return uwShared
    }

    class func destroy() {
        privateShared = nil
    }

    private init() {}
}

 

static 키워드로 설정된 타입 프로퍼티는 지연 저장 프로퍼티처럼 처음 접근할 때 초기화되지만

여러 스레드에서 동시에 접근해도 단 한번만 초기화되는 것을 보장하며

마찬가지로 공식 Swift 가이드에 관련 설명이 나와있다. 

 

(처음 프로퍼티에 접근할 때 초기화 되는 특징을 'lazily' 하다 라고 표현한다)

Stored type properties are lazily initialized on their first access. They’re guaranteed to be initialized only once, even when accessed by multiple threads simultaneously, and they don’t need to be marked with the lazy modifier.

 

하지만 privateShared 프로퍼티는 var 키워드로 설정돼있어 언제든지

그 값이 변경될 수 있을 뿐더러 nil 값을 가질 수도 있다.

 

이 말은 즉 여러 스레드에서 동시에 privateShared 프로퍼티에 접근했을때

원하지 않은 결과를 얻을 수도 있음을 의미한다.

 

뿐만 아니라 싱글톤 객체의 인스턴스가 여러번 생성되고 메모리에서 해제될 수 있도록

구현하는 것은 싱글톤의 올바른 사용법이 아니다.

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

따라서 let 키워드를 사용한 타입 프로퍼티를 선언함으로써 프로퍼티가 처음 사용될 때

초기화 된 이후 값이 변하지 않게 만들어 Thread-Safe한 싱글톤 디자인 패턴을 구현할 수 있는 것이다.

 

※ 전역 변수 역시 타입 프로퍼티(static)와 마찬가지로 atomic하기 때문에

let 키워드로 선언된 전역 변수는 Thread-Safe하다.

 

원자성(atomicity)이 무엇인지는 관련 문서만 링크하고 나중에 자세히 알아보도록 하겠다.

 

 


참고 링크

https://stackoverflow.com/questions/34046940/how-to-reset-singleton-instance

https://www.reddit.com/r/iOSProgramming/comments/a9fqkr/remove_singleton_instance_in_swift/

https://brunch.co.kr/@tilltue/71

https://github.com/hcn1519/TILMemo/issues/16

'Swift' 카테고리의 다른 글

[Swift] COW(Copy-on-Write)에 대하여  (0) 2022.07.28
[Swift] JSON 객체를 구조체로 변환하기  (0) 2022.05.16
[Swift] 초기화(Initialization)란?  (0) 2022.05.12