
self 를 참조하려 했지만 컴파일 에러 발생self 가 아닌 UploadApi.shared 변수를 참조하도록 구현class UploadApi: NSObject {
static let shared = UploadApi()
private init() {
super.init()
}
// self 대신 shared 변수 참조
private let session: URLSession = URLSession(configuration: .default, delegate: UploadApi.shared, delegateQueue: .current)
func upload(_ data: Data) {
session.uploadTask....
}
}
extension UploadApi: URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
let uploadProgress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
print(uploadProgress)
}
}
func upload(_ data: Data) {
// Error!! 실행 직후 에러 발생
UploadApi.shared.upload(data)
}


무한 재귀 발생과정
UploadApi.shared 를 사용하는 시점에서 UploadApi 객체 초기화 시작UploadApi 객체를 초기화 하기 위해 내부 프로퍼티인 session 변수의 초기화 시작UploadApi.shared 가 참조됨UploadApi.shared 변수는 현재 초기화 과정중에 있어 해당 변수를 사용하기 위해 다시 URLSession 인스턴스를 참조하게 됨UploadApi.shared의 init을 위해 URLSession을 참조하고, URLSession의 init은 UploadApi.shared을 참조하게 됨
Swift는 전역변수 초기화를 싱글톤 패턴과 같은 복잡한 초기화 방식과 퍼포먼스를 위해, lazy한 방식으로 초기화 한다.
(변수를 사용하는 시점에 초기화 하는 방식을 lazy하다고 표현)
lazy하게 초기화 하는 방식은 여러 쓰레드가 동시에 전역변수에 접근했을 때 race-condition을 발생시킬 수 있는데, 이 문제점을 dispatch_once를 통해 atomic하게 실행되어 한번만 초기화 되는 것을 보장한다.
이러한 개념을 통해 나는 static 변수는 반드시 thread-safe하게 초기화를 실시한다고 단편적으로 생각하였고, 전역변수를 초기화할 때 다시 초기화를 위해 대기중인 전역변수를 참조하여 데드락이 발생하게 되었다.
static 변수 초기화 방식과 무한 재귀 발생과 무관하여 제거되었습니다.
class UploadApi: NSObject {
static let shared = SomeApi()
private init() {
super.init()
}
// lazy 키워드로 초기화 시점을 미루고 self를 참조함
private lazy var session: URLSession = URLSession(configuration: .default, delegate: self, delegateQueue: .current)
func upload(_ data: Data) {
session.uploadTask....
}
}
class UploadApi: NSObject {
static let shared = UploadApi()
private init() {
super.init()
// super.init이 완료된 이후에 session 초기화
self.session = URLSession(configuration: .default, delegate: self, delegateQueue: .current)
}
private var session: URLSession!
func upload(_ data: Data) {
session.uploadTask....
}
}
위에서 발생했던 상황 이외에도, 여러개의 싱글톤이 각각의 초기화 시점에 다른 싱글톤 객체를 의존하고 있으면 동일하게 무한 재귀 상황이 발생한다.
‘static 변수 == thread-safe’ 라고 단순히 생각하며 개발하기 보단, static 변수가 어떤 시점에 thread-safe한지 정확하게 이해하고 개발하는 것이 중요하다고 생각된다.
Circular singletons, EXC_BREAKPOINT, shared.unsafeMutableAddressor - Swift Forums
lazy var 없이는 살 수 없게 되어 버렸습니다..