Skip to content

❗ DI Container 에서 생성한 인스턴스가 동시에 존재 하는 이슈

SeungJae Son edited this page Dec 1, 2024 · 1 revision

상황

DI Container 도입을 하며 하나의 Main Repository를 만들게 되고,

다른 서브 Repo(Player, Roominfo 등등) 들은 Main Repo를 의존하도록 등록하였다.

하지만 정상적으로 데이터가 뷰모델까지 흐르지 않았다. 왜 VM까지 데이터가 흐르지 않는가에 대한 원인을 발견했다.

디버깅을 통해 ViewModel이 갖고있는 MainRepo의 주소는 같으나,

서브 Repo 가 갖고있는 MainRepo의 주소가 다른 것을 확인하였다.

따라서 send()를 호출하는 MainRepository와 구독하는 MainRepository가 서로 다른 인스턴스였다.

error 위가 ViewModel에서 참조하고있는 MainRepository의 주소이고, Player Repository에서 참조하고있는 MainRepository와 다른 것을 확인할 수 있었습니다.

MainRepository의 인스턴스가 여러 개 존재 한다는 뜻인데 왜 이렇게 발생했을까 ?

문제의 코드

import ASContainer
import ASNetworkKit

public struct RepsotioryAssembly: Assembly {
    public init() {}
    
    public func assemble(container: Registerable) {
        
        container.register(MainRepositoryProtocol.self) { r in
            let databaseManager = r.resolve(ASFirebaseDatabaseProtocol.self)
            return MainRepository(
                databaseManager: databaseManager
            )
        }
       
        
        container.register(PlayersRepositoryProtocol.self) { r in
            let mainRepository = r.resolve(MainRepositoryProtocol.self)
            return PlayersRepository(
                mainRepository: mainRepository
            )
        }
        
       ...
    }
}

여기서 container에 등록할때 MainRepository를 생성하는 Closure를 저장하고 있었다. (현재 DI Container는 메모리 누수를 방지하기 위해 Resolve할 때마다 생성하는 방식으로, Closure로 저장하고 있음)

문제는 하나여야만 하는 객체인 MainRepository를 resolve 할때마다 항상 새로 생성해주고 있었던 것.

이를 해결하기 위해 싱글톤인 객체에 대해 하나의 객체만 만들어줄 필요가 있었다.

이를 기존 DIContainer 에서 조금 수정해서

싱글톤을 등록할 수 있는 함수를 작성했다. 그리고 resolve 할때는 싱글톤객체인지 확인하는 코드까지 추가하여 문제를 해결했다.

public protocol Registerable {
    func register<T>(_ type: T.Type, factory: @escaping (Resolvable) -> T)
    func register<T>(_ type: T.Type, _ object: T)
    func registerSingleton<T>(_ type: T.Type, factory: @escaping (Resolvable) -> T)
    func registerSingleton<T>(_ type: T.Type, _ object: T)
}

이로써 싱글톤 객체에 대해 전부 동일한 인스턴스를 가지게되었다 !!

  • 전체코드

    public protocol Registerable {
        func register<T>(_ type: T.Type, factory: @escaping (Resolvable) -> T)
        func register<T>(_ type: T.Type, _ object: T)
        func registerSingleton<T>(_ type: T.Type, factory: @escaping (Resolvable) -> T)
        func registerSingleton<T>(_ type: T.Type, _ object: T)
    }
    
    public protocol Resolvable {
        func resolve<T>(_ type: T.Type) -> T
    }
    
    public protocol Assembly {
        func assemble(container: Registerable)
    }
    
    public final class DIContainer: Registerable, Resolvable {
        public nonisolated(unsafe) static let shared = DIContainer()
        private init() {}
        
        private var factories = [String: (Resolvable) -> Any]()
        private var singletons = [String: Any]()
        
        public func register<T>(_ type: T.Type, factory: @escaping (Resolvable) -> T) {
            let key = "\(type)"
            factories[key] = factory
        }
        
        public func register<T>(_ type: T.Type, _ object: T) {
            let key = "\(type)"
            factories[key] = { _ in object }
        }
        
        public func registerSingleton<T>(_ type: T.Type, factory: @escaping (Resolvable) -> T) {
            let key = "\(type)"
            factories[key] = { [weak self] resolver in
                if let instance = self?.singletons[key] as? T {
                    return instance
                } else {
                    let instance = factory(resolver)
                    self?.singletons[key] = instance
                    return instance
                }
            }
        }
        
        public func registerSingleton<T>(_ type: T.Type, _ object: T) {
            let key = "\(type)"
            singletons[key] = object
        }
        
        public func resolve<T>(_ type: T.Type) -> T {
            let key = "\(type)"
            
            if let instance = singletons[key] as? T {
                return instance
            }
            
            guard let factory = factories[key] else {
                fatalError("등록되지 않은 의존성 타입: \(type)")
            }
            
            guard let dependency = factory(self) as? T else {
                fatalError("의존성 팩토리가 \(type) 타입의 인스턴스를 반환하지 않았습니다")
            }
            
            return dependency
        }
        
        public func addAssemblies(_ assemblies: [Assembly]) {
            assemblies.forEach { $0.assemble(container: self) }
        }
    }

iOS07 프로젝트 일지

📚 문서

🫶🏻 팀 기록

🎤 프로젝트

💡 핵심 경험

🚨 트러블 슈팅

📔 학습 정리

🪄 QA

Clone this wiki locally