따라서 인스턴스를 하나만 만들어야하는 클래스가 필요하다!
- 해당 클래스의 인스턴스가 하나만 만들어지고, 어디서든지 그 인스턴스에 접근할 수 있도록 하기 위한 패턴
- 싱글톤 패턴을 사용하면 전역 변수를 사용할 때와 마찬가지로 객체 인스턴스를 어디서든지 액세스할 수 있도록 할 수 있음
public class Singleton {
private static Singleton uniqueInstance;
// 기타 인스턴스 변수
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
// 기타 메소드
}
이렇게 하면 인스턴스가 필요한 상황이 닥치기 전에는 아예 인스턴스를 생성하지 않게 되고 이런 방법을 게으른 인스턴스 생성 lazy instantiation
이라고 부른다.
그냥 보기에는 코드에 문제가 없어보이지만 만약 멀티 스레드로 가동한다면 어떻게 될까?
이런 식으로 싱글톤인데도 불구하고 두 개의 인스턴스가 생성되게 된다.
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static synchronized Singleton getInstance() { // 동기화
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
public class Singleton {
private static Singleton uniqueInstance = new Singleton(); // 생성
private Singleton() {}
public static synchronized Singleton getInstance() {
return uniqueInstance;
}
}
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
참고
volatile 키워드를 사용하면 자바의 일종의 최적화인 리오더링(보통 컴파일 과정에서 일어나며, 프로그래머가 만들어낸 코드는 컴파일 될 때 좀더 빠르게 실행될 수 있도록 조작이 가해져 최적하됨)을 회피하여 읽기와 쓰기순서를 보장 멀티스레딩을 쓰더라도 uniqueInstance변수가 Singleton 인스턴스로 초기화 되는 과정이 올바르게 진행되도록 할 수 있다. 하지만 DCL은 자바1.5이상의 버전에서만 사용가능하다. 자바 1.4 및 그 전에 나온 버전의 JVM 중에는 volatile 키워드를 사용하더라도 동기화가 잘 안되는 것이 많다.
static
을 사용해 타입 프로퍼티로 인스턴스를 생성하면 사용 시점에 초기화(lazy)된다.
따라서 Singleton Instance가 최초 생성되기 전까진 메모리에 올라가지 않고, Dispatch_once도 자동 적용되어서 별 코드 없이도 Instance가 여러 개 생성되지 않는, Thread-Safe한 Singleton이 된다.
우리의 swift는 별다른 코드없이도 static
으로 인스턴스를 생성할 경우에 Thread-Safe하다고 합니다..👍
Swift로 싱글턴 패턴은 아래와 같이 만들 수 있다.
아래의 코드는 싱글턴을 정의하는데에 필요한 기본 코드이다.
필요에 따라서 코드를 추가로 작성하면 된다.
class Singleton {
// 1
static let defaults = Singleton()
// 2
private init() { }
}
static
으로 정의한 후, 자기 자신을 할당해준다. (프로퍼티의 이름이 정해져있지는 않지만 보통 shared, common, defaults, basic 등의 이름을 자주 사용함)class는 참조 타입, struct는 값 타입이다.
따라서 struct로 싱글톤을 만들면, 싱글톤 객체를 인스턴스화할 때 유일하지 못한 객체가 되게 된다.
struct Singleton {
static let shared = Singleton()
private init() { }
}
func address(of object: UnsafeRawPointer) -> String {
let address = Int(bitPattern: object)
return String(format: "%p", address)
}
var singleton1 = Singleton.shared
var singleton2 = Singleton.shared
print(address(of: &singleton1))
print(address(of: &singleton2))
/*
0x1030fca90
0x1030fca98
*/
만약 구조체를 인스턴스화해서 사용하지 않는다면 싱글톤처럼 사용할 수 있다.
swift에서의 static (타입)저장 프로퍼티는 자동으로 lazy 연산되니까, 객체를 한번도 사용하지 않는다고 하더라도 메모리적인 부분에서 문제점이 없다. 다중 스레드일 경우에도 한번만 초기화되는 것을 보장한다.
static (타입)메소드는 인스턴스를 만들 필요없이 접근 가능하고..
static은 상속 후 재정의가 불가능하다는 점, 싱글톤은 상속이 가능하다는 점이 있는 것 같다.
그럼 static 변수, 메소드를 사용하지 않고 따로 싱글톤을 만들어서 쓰는 이유는 뭐지? 라는 생각이 정리하면서 들었다..
stackoverflow - Difference between static function and singleton class in swift
주요 목적이 임시 핸들 외에 변수에 데이터를 저장할 필요가 없는 논리적 계산/조작 또는 작업을 제공하는 것이라면 항상 정적 클래스를 선택해야 합니다. 예를 들어 여기에서 언급한 유틸리티 클래스입니다. 이미지 크기 조정, 파일 읽기, 데이터 구조 구문 분석과 같은 유틸리티 방법은 정적 방법을 사용하는 것이 가장 좋습니다.
클래스가 여러 변수에 중요한 정보를 저장해야 하는 경우 리소스 액세스 제한(예: 데이터베이스의 동시 트랜잭션 수 제한 또는 동시 네트워크 호출 수 제한), 자주 리소스를 할당 및 할당 해제(런타임에 메모리 관리)하는 경우 다음을 수행하는 것이 가장 좋습니다. Singleton 클래스 사용(예: db 구조에 액세스하기 위한 클래스, 네트워크 리소스 관리 등)
데이터(특히 중요한 데이터)를 저장하는 게 주 목적인 경우에는 싱글톤을 사용하고,
딱히 데이터를 저장할 필요없고 단순히 논리적 계산, 조작 또는 연산이 필요할 경우에는 정적 메소드와 정적 변수가 있는 클래스를 쓰는게 적절하다는 말 같다!
읽어보니 어느정도 이해가 된다. 정말 하나의 인스턴스가 있어야 하고 그것이 공유되어야하는 이유가 있는 경우에는 싱글톤을 사용하고, 그게 아닌 경우에는 static을 쓰면 될 것 같다.
케로로빵으로 바꿔주세요.