이 글은 Sachithra Siriwardhane 님의 글 Thread-safe singletons and their usage in Swift를 바탕으로 작성되었습니다.
Thread-safe한 싱글톤(singleton)과 그 사용법
개발자라면 다들 ‘싱글톤 패턴(singleton pattern)’에 대해 알고 있을 것이고 사용해 보셨을 것이며, iOS 어플리케이션에서 구현이 가능하실 것입니다. 이 글은 입문자 또는 싱글톤 디자인 패턴(design pattern)을 적절하게 사용하기 위한 배경 지식 향상을 원하는 분들에게 도움이 되어 드리고자 작성되었습니다. 이 글에서는 다음과 같은 주제들을 다뤄볼 것입니다.
- 싱글톤 패턴의 사용: 해야 할 것과 하지 말아야 할 것
- Swift/Objective-C에서 싱글톤 패턴의 작성
- 동시성 문제 해결 및 thread-safe하게 싱글톤 패턴을 작성하기
Swift/Objective-C에서 싱글톤 패턴의 작성
다음 소스는 데이터를 수집하여 관련된 호출자에게 이 데이터를 반환하기 위해 준비된 EventLogger
클래스를 싱글톤으로 작성한 기본적인 예제입니다.
// Swift
import Foundation
final public class EventLogger {
public static let shared = EventLogger()
private var eventFired: [String: Any] = ["initialization":"Property initialized"]
private init() {
}
private func readLog(for key: String) -> String? {
return eventFired[key] as? String
}
private func writeLog(key: String, content: Any) {
eventFired[key] = content
}
}
아래는 Swift 코드에 해당하는 Objective-C 코드이지만 완전히 일치하지는 않을 것입니다.
// Objective-C
@interface EventLogger : NSObject
@end
@implementation EventLogger {
NSDictionary * eventFired;
}
+ (instancetype)sharedEventLogger {
static dispatch_once_t once = 0;
static id sharedInstance = nil;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
if (self = [super init]) {
self->eventFired = [[NSDictionary alloc] initWithObjectsAndKeys:@"initialization",@"Property initialized", nil];
}
return self;
}
- (void)dealloc {
// ...
}
- (NSString *)readLogForKey:(nonnull NSString *)key {
return [self->eventFired valueForKey:key];
}
- (void)writeLogForKey:(nonnull NSString *)key andContent:(nullable id)content {
[self->eventFired setValue:content forKey:key];
}
@end
여기서 EventLogger
는 파생 클래스가 생겨나지 않도록 final
키워드로 선언되었습니다.
public static let shared = EventLogger()
shared
라고 이름이 적힌 이 상수는 EventLogger
클래스형 인스턴스를 참조하는 싱글톤입니다. 여기서 static
키워드는 클래스 자체에 소속된 프로퍼티이지 그 클래스의 인스턴스에 소속된 프로퍼티가 아니라는 뜻입니다. 이는 해당 프로퍼티를 보다 전역적인 범위에서 접근 가능하도록 해 줍니다.
private init()
클래스의 생성자는 클래스 외부에서 새 인스턴스가 만들어지지 못하도록 private
로 선언되었는데 이는, 이 클래스의 생성자가 이 클래스 내부적으로만 호출될 수 있도록 보장해 줍니다.
다음은 싱글톤이 사용되는 예입니다. readLog
메소드가 옵셔널 문자열을 반환함에 따라 값을 ‘언랩(unwrap)’하여 사용하기 위해 옵셔널 바인딩이 사용되었습니다.
// Swift
if let contents = EventLogger.shared.readLog(for: "initialization") {
print(contents)
} else {
print("File not found")
}
// Objective-C
NSString * contents = [[EventLogger sharedEventLogger] readLogForKey:@"initialization"];
if (contents != nil) {
NSLog(contents);
} else {
NSLog(@"File not found");
}