싱글턴 패턴에 관해서 이야기는 자주 들어봤지만,
자세히 알아보니 놓쳤던 부분이 많아서 정리를 하게 되었습니다,,
우리가 보통 알고 있는 내용은
오직 한 개의 클래스 인스턴스만을 갖도록 보장하고, 이에 대한 전역적인 접근점을 제공한다.
정도가 되겠네요!
언제 사용하지??
class UserInfo {
var name: String?
var address: String?
var number: String?
}
UserInfo라는 클래스를 사용해서 회원가입을 진행하는 상황이라고 생각을 해봅시다.
그런데 이제 한 개의 뷰컨트롤러가 아닌 세 개의 뷰컨트롤러에서 각각 name, address 그리고 number를 입력받아서 저장해야 하는 상황!
// userInfo의 name 설정
class VC1: UIViewController {
var userInfo = UserInfo()
...
func setUserName(name: String) {
userInfo.name = name
}
}
// userInfo의 address 설정
class VC2: UIViewController {
var userInfo = UserInfo()
...
func setUserName(address: String) {
userInfo.address = address
}
}
// userInfo의 number 설정
class VC3: UIViewController {
var userInfo = UserInfo()
...
func setUserName(number: String) {
userInfo.number = number
}
}
그래서 이런 엄청난 코드를 짜게 되면... (ㅇ...이게 뭐야?!)
뷰컨트롤러마다 UserInfo를 생성했기 때문에 서로 다른 UserInfo에 값을 세팅하고 있는 것이 되겠죠
즉, 인스턴스(여기서는 UserInfo)를 개별적으로 생성하고 있습니다!
하지만 우리는 세 개의 뷰컨트롤러에서 각각 데이터를 입력받아한 개의 UserInfo에 데이터를 저장하고 싶습니다!
물론, VC1에서 UserInfo를 생성하고 name을 저장한 뒤 다음 뷰컨인 VC2에 이 UserInfo를 넘겨주는 방식도 있겠지만
뷰컨트롤러 사이에 참조를 계속 넘겨줘야 하는 번거로움이 있습니다. (인스턴스는 참조타입)
이럴 경우에 사용할 수 있는 것이 싱글턴 패턴입니다.
클래스에 대한 인스턴스는 최초 생성될 때 한 번만 생성하여 전역으로 둡니다.
그 이후에는 생성된 인스턴스에만 접근 가능하게 하는 디자인패턴입니다.
싱글턴 패턴
전역적인 접근점을 제공하기 위해서 Swift에서는 타입 프로퍼티 static을 사용하여 구현합니다.
기본적인 프로퍼티들은 인스턴스마다 다른 값을 가질 수 있지만,
타입 프로퍼티는 인스턴스를 여러개 생성해도 값이 1개만 존재하게 됩니다.
위에서 세 개의 뷰컨트롤러 안에 UserInfo를 각각 생성해서 세 개의 인스턴스를 만들었습니다.
그 안에 들어간 값들이 전부 달랐었죠?
- vc1 - UserInfo(name: name, address: nil, number: nil)
- vc2 - UserInfo(name: nil, address: address, number: nil)
- vc3 - UserInfo(name: nil, address: nil, number: number)
class UserInfo {
// 타입 프로퍼티 static 사용
static let userInfo = UserInfo()
var name: String?
var address: String?
var number: String?
}
하지만 이렇게 static을 사용하여 한 개의 인스턴스(UserInfo)를 두고,
각 뷰컨트롤러에서 이 값을 변경하게 된다면 처음에 원했던 것들(한 개의 인스턴스에 각 데이터 저장)을 수행할 수 있습니다!
// userInfo의 name 설정
class VC1: UIViewController {
func setUserName(name: String) {
UserInfo.userInfo.name = name
}
}
// userInfo의 address 설정
class VC2: UIViewController {
func setUserName(address: String) {
UserInfo.userInfo.address = address
}
}
// userInfo의 number 설정
class VC3: UIViewController {
func setUserName(number: String) {
UserInfo.userInfo.number = number
}
}
static으로 바꾼 뒤 이렇게 사용하게 되면
실질적으로 인스턴스는 한 번만 생성(UserInfo class에서)됐기 때문에
각 뷰컨트롤러에서 같은 인스턴스에 접근할 수 있게 됩니다!
다시 정리를 해보면
타입 프로퍼티: 인스턴스 생성 여부와 상관없이 값이 한 개만 존재
싱글턴 패턴: 클래스에 대한 인스턴스는 최초 생성 시 한 번만 생성해서 전역으로
이렇게 정의가 동일하기 때문에 싱글턴 패턴 구현 시 타입 프로퍼티를 사용합니다!
class UserInfo {
// 타입 프로퍼티 static 사용
static let userInfo = UserInfo()
private init() { }
var name: String?
var address: String?
var number: String?
}
추가적으로 최초 생성 시 한 번만 생성해야 하기 때문에 init() 메서드의 접근 제어자를 private로 설정하면 됩니다.
이렇게 설정하면 UserInfo 클래스 외부에서 임의로 UserInfo를 생성하는 것이 불가능하겠죠?
장점과 단점
일단 static을 사용했기 때문에 어느 클래스에서도 전역변수처럼 접근이 가능합니다.
한 번만 인스턴스를 생성하기 때문에(타입 프로퍼티) 메모리 낭비를 방지할 수 있습니다.
또한 전역변수를 사용하게 되면, 사용하지 않을 때에도 메모리를 사용하지만,
싱글턴을 사용하면 생성 시점부터 메모리를 할당(lazy)하고, 사용하지 않을 때 해제가 가능합니다.
하지만 private를 사용해서 생성자 접근을 제한했기 때문에,
해당 테스트용 인스턴스를 생성하기 불편해지기 때문에
로직 테스트를 하는 경우에 제한이 될 수 있습니다.
또한 어느 클래스에서도 접근이 가능하기 때문에
무분별하게 사용할 경우(여러 클래스에서 해당 인스턴스에 접근)
어떤 객체와 해당 인스턴스가 연결되었는지 찾기 어려워지는 문제가 발생합니다.
멀티스레드 환경에서도 위험한데요,
예를들어
같은 싱글턴 객체를 메인 스레드와 백그라운드 스레드에서 작업을 하게 되면
문제가 발생하겠죠?
싱글턴 내부 데이터를 동기처리 하지 않으면 동시 접근에 대한 보호를 받을 수 없습니다.
그래서 Reader - Writer Lock 패러다임을 사용한다고 하는데!
여러 스레드에서 동시에 읽는 것은 가능하지만,
데이터 쓰기 및 수정에는 배타적인 잠금을 설정하는 방식입니다.
그러니까...
데이터를 읽을 때에는 concurrent queue를 사용해서 sync 하게 처리를 하고,
데이터를 쓰거나 수정할 때에는 async와 block을 사용해서 완료될 때까지 대기하는 방식으로 설계하면 됩니다!
class UserInfo {
static var userInfo = UserInfo()
// queue를 사용해서 읽기 / 쓰기 제어
private let concurrentQueue = DispatchQueue(label: "concurrentQueue",
attributes: .concurrent)
var name: String?
var address: String?
var number: String?
// 읽을 때에는 sync하게 동작
func readUserName() -> String {
concurrentQueue.sync {
return self.name ?? "NoData"
}
}
// async + bloack으로 이전 수행 동작이 처리된 후에 동작
func writeUserName(name: String) {
concurrentQueue.async(flags: .barrier) {
self.name = name
}
}
}
스레드로부터 안전하게 수정하면 이 정도가 되겠네요.
세마포어 사용으로도 멀티스레드로 부터 안전하게 사용을 할 수 있는데!
이 친구는 다음에 알아보도록 하겠습니다 ㅎㅎ,,
참고 블로그 👍
https://i-colours-u.tistory.com/38?category=1004905
'iOS > 디자인패턴' 카테고리의 다른 글
swift - MVVM 패턴 (feat. MVC) (1) | 2022.05.20 |
---|