Skip to content

Comments

Setting/#7 DIContainer#8

Merged
y-eonee merged 5 commits intodevelopfrom
setting/#7-dicontainer
Jan 7, 2026
Merged

Setting/#7 DIContainer#8
y-eonee merged 5 commits intodevelopfrom
setting/#7-dicontainer

Conversation

@y-eonee
Copy link
Contributor

@y-eonee y-eonee commented Jan 5, 2026

🔗 연결된 이슈

📄 작업 내용

  • DIContainer 세팅을 했습니다.
  • DIContainer 세팅을 위한 테스트용 코드를 작성했습니다.
구현 내용 IPhone 13 mini
GIF
image

💻 주요 코드 설명

DIContainer란?

어제 미미나때도 나연언니가 언급해준 내용이기도 한데요. DIContainer는 Dependency Injection Container로 의존성 주입을 관리해주는 아이입니다. DIContainer가 의존성을 모두 관리하며 주입해줍니다.
만약 DIContainer 없이 그냥 우리 프로젝트 구조에서 의존성주입을 한다면, viewModel을 부를 때
뷰모델(유스케이스(레포지토리..))) 이렇게 영원히 의존성 주입을 해야하는 복잡한 코드가 나오게 됩니다.
그래서 모든 의존성을 관리해주는 DIContainer를 사용합니다.

구현

final class DIContainer {
    //싱글톤으로 사용 
    static let shared = DIContainer()
    
    private init() { }
    
// 의존성을 저장하는 딕셔너리입니다. String을 Key로, 클로저를 value로 저장합니다. 
    private var dependencies: [String: () -> Any] = [:]
}

dependency를 관리하는 딕셔너리를 만듭니다.

인스턴스를 관리하는 데에는 register와 resolve 두가지 방법이 있습니다.

func register<T>(type: T.Type, closure: @escaping () -> T) {
    let key = String(describing: T.self) // 타입 이름을 문자열로 반환
    dependencies[key] = closure
}

register는 내가 사용할 모든 인스턴스를 딕셔너리에 등록합니다.
key에는 타입이름을 String으로 저장하고, value에는 인스턴스를 만들어줄 클로저를 저장합니다.
dependencies의 Value에 () -> Any라는 클로저를 지정해주었기 때문에, register 할 때에는 T 객체를 만들어주는 클로저가 들어가게 됩니다.

  func resolve<T>(type: T.Type) -> T? {
      let key = String(describing: T.self)
      guard let closure = dependencies[key] else {
          return nil
      }
      
      return closure() as? T // 클로저 실행 후 T로 캐스팅
  }

resolve는 내가 객체가 필요한 시점에 컨테이너에게 특정 타입의 인스턴스를 달라고 하는 과정입니다.
dependecies 딕셔너리에서 key에 해당하는 클로저를 꺼내와서 실행합니다.

Layer 별 DI

DI되는 순서는 다음과 같습니다.

Data 레포지토리 -> Domain UseCase -> Presentation ViewModel 

그래서 레이어 별로 register하고, 필요할 때 resolve해오는 코드를 작성합니다.

ViewFactory

뷰를 생성할 때에도 ViewModel이 필요합니다. 그래서 ViewFactory를 만들어 이곳에서 모두 관리합니다.

앱 진입 시 DI

앱 진입 시에 DIContainer의 dependencyInjection 함수를 호출 해 레이어별로 모두 register합니다.
추후에 코디네이터로 옮길 생각입니다.

To Reviewers

리뷰 많이 달아주세요~ ~~~ 질문도 편하게 하세요 !!

Summary by CodeRabbit

  • New Features

    • Added a new interactive test interface with button-driven functionality that provides responsive user feedback.
  • Refactor

    • Redesigned the application architecture by implementing a modular dependency management system for improved code organization and maintainability.
  • Chores

    • Removed deprecated view components and cleaned up legacy code files.

✏️ Tip: You can customize this high-level summary in your review settings.

@y-eonee y-eonee requested a review from a team January 5, 2026 16:59
@y-eonee y-eonee self-assigned this Jan 5, 2026
@y-eonee y-eonee requested review from soseoyo12, sum130 and wotjs020708 and removed request for a team January 5, 2026 16:59
@y-eonee y-eonee added 나연🐹 Setting 프로젝트 세팅 시 사용 labels Jan 5, 2026
@y-eonee y-eonee linked an issue Jan 5, 2026 that may be closed by this pull request
1 task
@coderabbitai
Copy link

coderabbitai bot commented Jan 5, 2026

📝 Walkthrough

Walkthrough

This PR establishes a layered dependency injection system using a multi-assembler pattern (Data → Domain → Presentation), initializes the DI container at app startup, replaces placeholder views with a complete test feature demonstrating the DI flow, and removes legacy placeholder files.

Changes

Cohort / File(s) Summary
DI Core Infrastructure
Cherrish-iOS/Cherrish-iOS/Core/DIContainer.swift, Cherrish-iOS/Cherrish-iOS/App/DIContainer+.swift
Introduces DIContainer singleton with register/resolve methods using type-keyed closures; adds DIContainer extension with dependencyInjection() method that chains three assemblers (Data → Domain → Presentation)
Data Layer
Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift, Cherrish-iOS/Cherrish-iOS/Data/Repository/TestRepository.swift, Cherrish-iOS/Cherrish-iOS/Data/Repository/Repository.swift
Adds DataDependencyAssembler registering DefaultTestRepository for TestInterface; creates concrete DefaultTestRepository; removes legacy Repository.swift
Domain Layer
Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift, Cherrish-iOS/Cherrish-iOS/Domain/Interface/TestInterface.swift, Cherrish-iOS/Cherrish-iOS/Domain/UseCase/TestUseCase.swift
Adds DomainDependencyAssembler delegating to data layer and registering DefaultTestUseCase; introduces TestInterface protocol and TestUseCase protocol with DefaultTestUseCase implementation
Presentation Layer
Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift, Cherrish-iOS/Cherrish-iOS/Presentation/ViewFactory.swift, Cherrish-iOS/Cherrish-iOS/Presentation/Feature/TestView.swift, Cherrish-iOS/Cherrish-iOS/Presentation/Feature/TestViewModel.swift
Adds PresentationDependencyAssembler registering TestViewModel; creates ViewFactory with static makeTestView() method; introduces TestView (SwiftUI) and TestViewModel (ObservableObject) for test feature
App Entry Point
Cherrish-iOS/Cherrish-iOS/App/Cherrish_iOSApp.swift
Adds initializer invoking DIContainer.shared.dependencyInjection(); replaces root ContentView() with ViewFactory.makeTestView()
Cleanup
Cherrish-iOS/Cherrish-iOS/Domain/UseCase/Usecase.swift, Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ContentView.swift
Removes legacy placeholder files (empty Usecase.swift and basic ContentView.swift)

Sequence Diagram

sequenceDiagram
    participant App as App Launch
    participant DI as DIContainer
    participant DataAsm as Data Assembler
    participant DomainAsm as Domain Assembler
    participant PresentationAsm as Presentation Assembler
    participant Factory as ViewFactory
    participant VM as TestViewModel

    App->>DI: init() calls dependencyInjection()
    
    rect rgb(200, 220, 255)
    Note over DataAsm,PresentationAsm: DI Initialization Chain
    DI->>DataAsm: create & assemble()
    DataAsm->>DI: register TestInterface → DefaultTestRepository
    
    DI->>DomainAsm: create with DataAsm as preAssembler & assemble()
    DomainAsm->>DataAsm: assemble()
    DataAsm->>DI: (re-register data dependencies)
    DomainAsm->>DI: resolve TestInterface
    DI-->>DomainAsm: DefaultTestRepository
    DomainAsm->>DI: register TestUseCase → DefaultTestUseCase
    
    DI->>PresentationAsm: create with DomainAsm as preAssembler & assemble()
    PresentationAsm->>DomainAsm: assemble()
    DomainAsm->>DI: (re-assemble domain dependencies)
    PresentationAsm->>DI: resolve TestUseCase
    DI-->>PresentationAsm: DefaultTestUseCase
    PresentationAsm->>DI: register TestViewModel with TestUseCase
    end

    rect rgb(220, 240, 220)
    Note over Factory,VM: View Creation
    App->>Factory: makeTestView()
    Factory->>DI: resolve TestViewModel
    DI-->>Factory: TestViewModel instance
    Factory-->>App: TestView with bound ViewModel
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • wotjs020708
  • sum130
  • soseoyo12

Poem

🐰 Dependency dreams in layers bright,
Data, Domain, Presentation light,
The container dances, assemblers twine,
TestView springs forth, all wired just fine!
From repo to viewModel, a DI delight!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Setting/#7 DIContainer' is partially related to the changeset. It references the issue number and the main feature (DIContainer), but lacks specificity about what was done—whether implementing, refactoring, or testing.
Linked Issues check ✅ Passed The pull request successfully implements all four primary objectives from issue #7: DIContainer introduction for centralized dependency management [#7], layer-based dependency flow (Data→Domain→Presentation) [#7], ViewFactory for ViewModel injection [#7], and app initialization setup [#7].
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #7 requirements. Deleted ContentView.swift (replaced by TestView for testing), removed empty Usecase.swift, and Repository.swift are cleanup tasks supporting the new DI architecture.
✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Fix all issues with AI Agents 🤖
In @Cherrish-iOS/Cherrish-iOS/App/DIContainer+.swift:
- Line 2: The file header comment currently says "AppDIContainer+.swift" but the
actual file is named "DIContainer+.swift"; update the top-of-file header comment
to match the real filename by replacing "AppDIContainer+.swift" with
"DIContainer+.swift" (or conversely rename the file if the header is
authoritative), ensuring the header and actual filename are consistent.

In @Cherrish-iOS/Cherrish-iOS/Core/DIContainer.swift:
- Around line 20-25: The dependencies dictionary is not thread-safe: mutations
in register(type:closure:) and reads in resolve can race; protect access to the
private dependencies by adding a synchronization primitive (e.g., a private
serial DispatchQueue or an NSLock) and wrap all reads and writes to dependencies
with that lock/queue (use sync for reads and async/sync or barrier for writes if
using a concurrent queue). Update register(type:closure:) and resolve to use
this synchronization so all accesses to dependencies are serialized and data
races are prevented.

In @Cherrish-iOS/Cherrish-iOS/Presentation/Feature/TestView.swift:
- Around line 1-2: Update the file header comment to match the actual filename
by replacing the incorrect "ContentView.swift" text with "TestView.swift" in the
top-of-file comment; ensure any other header metadata (if present) also reflects
TestView.swift so the header is consistent with the file and class/struct names
in TestView.

In
@Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift:
- Around line 17-27: In assemble(), remove the debug print statement inside the
DIContainer.shared.register closure (the line that prints "뷰모델 등록"); instead
either delete it or replace it with a call to the app logging facility (e.g.,
Logger.log or similar) if persistent logs are required; ensure the closure still
returns TestViewModel(testUseCase: testUseCase) and keep the guard resolving
TestUseCase unchanged.

In @Cherrish-iOS/Cherrish-iOS/Presentation/ViewFactory.swift:
- Around line 15-21: Replace the fatalError in makeTestView: when
DIContainer.shared.resolve(type: TestViewModel.self) returns nil, log the
failure (using the app logger or print), and return a safe fallback TestView
instance instead of crashing; implement this by either creating a lightweight
DefaultTestViewModel (or a TestViewModel.mock()/initWithDefaults()) and
returning TestView(viewModel: defaultVM), or add a TestView.init() convenience
initializer that creates an empty/placeholder UI and return that; update
makeTestView to use the fallback path and keep the method signature unchanged so
callers aren’t forced to handle throws.
🧹 Nitpick comments (8)
Cherrish-iOS/Cherrish-iOS/Domain/Interface/TestInterface.swift (1)

10-12: Consider adding explicit access control modifier.

The protocol serves as a public contract between Domain and Data layers. While Swift defaults to internal access, explicitly declaring internal protocol TestInterface (or public if cross-module usage is intended) improves code clarity and makes architectural boundaries more visible.

🔎 Proposed fix
-protocol TestInterface {
+internal protocol TestInterface {
     func test()
 }
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/TestView.swift (1)

10-22: LGTM! View correctly integrates with ViewModel via dependency injection.

The SwiftUI view properly uses @ObservedObject to observe the externally-injected ViewModel, and the Button action correctly triggers the ViewModel's test method while displaying reactive state.

Minor: Simplify string interpolation

Line 18 uses unnecessary string interpolation. Text can accept a String directly:

-                Text("\(viewModel.text)")
+                Text(viewModel.text)
Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift (1)

30-31: Remove trailing empty lines.

Clean up the unnecessary empty lines at the end of the file.

🔎 Proposed fix
     }
 }
-
-
Cherrish-iOS/Cherrish-iOS/Presentation/ViewFactory.swift (2)

10-12: Consider whether the protocol is necessary.

The protocol ViewFactoryProtocol is only implemented by ViewFactory. Unless you plan to have multiple implementations or need to mock this for testing, the protocol might be unnecessary overhead.


17-17: Address the TODO comment.

The TODO indicates missing fallback handling when DI resolution fails. This should be resolved before merging to ensure robust error handling.

Do you want me to open a new issue to track implementing the fallback behavior, or would you prefer to address it in this PR?

Cherrish-iOS/Cherrish-iOS/Core/DIContainer.swift (1)

22-25: Consider adding lifecycle management for different dependency scopes.

The current implementation always creates new instances on each resolve() call. Consider supporting different lifecycles:

  • Transient: New instance each time (current behavior)
  • Singleton: Single shared instance created once
  • Scoped: Instance per scope (e.g., per request/session)

This would provide more flexibility for production use beyond test scenarios.

Cherrish-iOS/Cherrish-iOS/Domain/UseCase/TestUseCase.swift (2)

2-2: Update file header to match actual filename.

The header comment references "Usecase.swift" but the actual filename is "TestUseCase.swift". Update for consistency.

🔎 Proposed fix
 //
-//  Usecase.swift
+//  TestUseCase.swift
 //  Cherrish-iOS

21-24: Consider replacing debug print with proper logging.

The print statement on line 23 is useful for testing but should be replaced with a proper logging framework for production code. If this is purely test/demo code, you may defer this change.

🔎 Example using OSLog (if targeting iOS 14+)
import os.log

private let logger = Logger(subsystem: "com.cherrish", category: "UseCase")

func execute() {
    self.repository.test()
    logger.debug("UseCase execute")
}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ecda80b and 2b06bf7.

📒 Files selected for processing (15)
  • Cherrish-iOS/Cherrish-iOS/App/Cherrish_iOSApp.swift
  • Cherrish-iOS/Cherrish-iOS/App/DIContainer+.swift
  • Cherrish-iOS/Cherrish-iOS/Core/DIContainer.swift
  • Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift
  • Cherrish-iOS/Cherrish-iOS/Data/Repository/Repository.swift
  • Cherrish-iOS/Cherrish-iOS/Data/Repository/TestRepository.swift
  • Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift
  • Cherrish-iOS/Cherrish-iOS/Domain/Interface/TestInterface.swift
  • Cherrish-iOS/Cherrish-iOS/Domain/UseCase/TestUseCase.swift
  • Cherrish-iOS/Cherrish-iOS/Domain/UseCase/Usecase.swift
  • Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ContentView.swift
  • Cherrish-iOS/Cherrish-iOS/Presentation/Feature/TestView.swift
  • Cherrish-iOS/Cherrish-iOS/Presentation/Feature/TestViewModel.swift
  • Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift
  • Cherrish-iOS/Cherrish-iOS/Presentation/ViewFactory.swift
💤 Files with no reviewable changes (3)
  • Cherrish-iOS/Cherrish-iOS/Data/Repository/Repository.swift
  • Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ContentView.swift
  • Cherrish-iOS/Cherrish-iOS/Domain/UseCase/Usecase.swift
🧰 Additional context used
🧬 Code graph analysis (10)
Cherrish-iOS/Cherrish-iOS/Domain/Interface/TestInterface.swift (2)
Cherrish-iOS/Cherrish-iOS/Data/Repository/TestRepository.swift (1)
  • test (11-13)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/TestViewModel.swift (1)
  • test (18-22)
Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift (2)
Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift (1)
  • assemble (17-27)
Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift (1)
  • assemble (17-28)
Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift (3)
Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift (1)
  • assemble (11-15)
Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift (1)
  • assemble (17-28)
Cherrish-iOS/Cherrish-iOS/Core/DIContainer.swift (2)
  • resolve (27-34)
  • register (22-25)
Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift (3)
Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift (1)
  • assemble (11-15)
Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift (1)
  • assemble (17-27)
Cherrish-iOS/Cherrish-iOS/Core/DIContainer.swift (2)
  • resolve (27-34)
  • register (22-25)
Cherrish-iOS/Cherrish-iOS/Data/Repository/TestRepository.swift (1)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/TestViewModel.swift (1)
  • test (18-22)
Cherrish-iOS/Cherrish-iOS/App/Cherrish_iOSApp.swift (2)
Cherrish-iOS/Cherrish-iOS/App/DIContainer+.swift (1)
  • dependencyInjection (11-17)
Cherrish-iOS/Cherrish-iOS/Presentation/ViewFactory.swift (1)
  • makeTestView (15-21)
Cherrish-iOS/Cherrish-iOS/Presentation/ViewFactory.swift (1)
Cherrish-iOS/Cherrish-iOS/Core/DIContainer.swift (1)
  • resolve (27-34)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/TestViewModel.swift (2)
Cherrish-iOS/Cherrish-iOS/Data/Repository/TestRepository.swift (1)
  • test (11-13)
Cherrish-iOS/Cherrish-iOS/Domain/UseCase/TestUseCase.swift (1)
  • execute (21-24)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/TestView.swift (2)
Cherrish-iOS/Cherrish-iOS/Data/Repository/TestRepository.swift (1)
  • test (11-13)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/TestViewModel.swift (1)
  • test (18-22)
Cherrish-iOS/Cherrish-iOS/Domain/UseCase/TestUseCase.swift (2)
Cherrish-iOS/Cherrish-iOS/Data/Repository/TestRepository.swift (1)
  • test (11-13)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/TestViewModel.swift (1)
  • test (18-22)
🪛 SwiftLint (0.57.0)
Cherrish-iOS/Cherrish-iOS/App/Cherrish_iOSApp.swift

[Warning] 14-14: TODOs should be resolved (코디네이터에서 실행하기)

(todo)

Cherrish-iOS/Cherrish-iOS/Presentation/ViewFactory.swift

[Warning] 17-17: TODOs should be resolved (DI 실패 시 기본으로 갈 곳 지정)

(todo)

🔇 Additional comments (14)
Cherrish-iOS/Cherrish-iOS/App/Cherrish_iOSApp.swift (2)

13-16: LGTM! DI initialization at app startup is appropriate.

The initialization pattern correctly wires up all dependency layers before the view hierarchy is constructed. The TODO comment appropriately flags the planned migration to a coordinator pattern.


20-20: LGTM! ViewFactory pattern correctly decouples view construction.

Using ViewFactory.makeTestView() properly delegates view construction to the factory, which handles ViewModel resolution from DIContainer. This maintains separation of concerns.

Cherrish-iOS/Cherrish-iOS/Presentation/Feature/TestViewModel.swift (2)

10-16: LGTM! ViewModel correctly implements dependency injection and SwiftUI state management.

The final class prevents unnecessary subclassing, @Published properly exposes state to SwiftUI, and constructor-based dependency injection cleanly decouples the ViewModel from concrete UseCase implementations.


18-22: LGTM! State update logic correctly follows the use case execution.

The method appropriately delegates business logic to the use case, then updates UI state. The console logging aids debugging during this test/demo phase.

Cherrish-iOS/Cherrish-iOS/Data/Repository/TestRepository.swift (1)

10-14: LGTM! Repository implementation correctly conforms to the domain interface.

Using a struct is appropriate for this stateless repository. The implementation correctly fulfills the TestInterface contract and demonstrates the Data layer in the DI flow.

Cherrish-iOS/Cherrish-iOS/App/DIContainer+.swift (1)

10-17: LGTM! Assembler chain pattern is well-structured.

The dependency injection setup correctly chains the assemblers in the proper layer order (Data → Domain → Presentation), allowing each layer to resolve dependencies from the previous layer.

Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift (1)

10-15: LGTM! Data layer registration is correct.

The assembler correctly registers the concrete repository implementation for the data layer interface. As the first assembler in the chain, it appropriately has no preAssembler dependency.

Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift (1)

10-15: LGTM! Assembler structure follows the established pattern.

The preAssembler dependency and initialization correctly implement the chain-of-responsibility pattern for multi-layer DI wiring.

Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift (1)

10-27: LGTM! Domain layer assembler correctly implements the pattern.

The assembler properly chains with the data layer assembler, resolves the repository interface, and registers the use case. The guard statement ensures safe handling when dependencies are unavailable.

Cherrish-iOS/Cherrish-iOS/Core/DIContainer.swift (3)

10-12: LGTM! Clean protocol definition.

The DependencyAssembler protocol provides a clear contract for dependency assembly across layers.


14-18: LGTM! Singleton pattern correctly implemented.

The final modifier and private initializer properly enforce singleton access.


27-34: LGTM! Safe optional resolution logic.

The method correctly returns nil when a dependency isn't registered, and the safe cast (as? T) prevents runtime crashes if type mismatches occur.

Cherrish-iOS/Cherrish-iOS/Domain/UseCase/TestUseCase.swift (2)

10-12: LGTM! Clean protocol abstraction.

The protocol provides a simple, clear contract for use case execution.


14-19: LGTM! Appropriate use of value semantics.

Using a struct with an immutable repository dependency is a good Swift practice for use cases without shared mutable state.

@@ -0,0 +1,18 @@
//
// AppDIContainer+.swift
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

File name mismatch in header comment.

The header comment references "AppDIContainer+.swift" but the actual filename is "DIContainer+.swift".

🔎 Proposed fix
-//  AppDIContainer+.swift
+//  DIContainer+.swift
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// AppDIContainer+.swift
// DIContainer+.swift
🤖 Prompt for AI Agents
In @Cherrish-iOS/Cherrish-iOS/App/DIContainer+.swift around line 2, The file
header comment currently says "AppDIContainer+.swift" but the actual file is
named "DIContainer+.swift"; update the top-of-file header comment to match the
real filename by replacing "AppDIContainer+.swift" with "DIContainer+.swift" (or
conversely rename the file if the header is authoritative), ensuring the header
and actual filename are consistent.

Comment on lines +20 to +25
private var dependencies: [String: () -> Any] = [:]

func register<T>(type: T.Type, closure: @escaping () -> T) {
let key = String(describing: T.self)
dependencies[key] = closure
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add thread-safety to prevent data races.

The dependencies dictionary is mutated in register and accessed in resolve without any synchronization. If multiple threads call these methods concurrently (e.g., during app initialization with async tasks, or from background threads), this can lead to data races and crashes.

🔎 Proposed fix using a serial queue or lock
 final class DIContainer {
     
     static let shared = DIContainer()
     
     private init() { }
     
+    private let queue = DispatchQueue(label: "com.cherrish.dicontainer", attributes: .concurrent)
     private var dependencies: [String: () -> Any] = [:]
     
     func register<T>(type: T.Type, closure: @escaping () -> T) {
         let key = String(describing: T.self)
-        dependencies[key] = closure
+        queue.async(flags: .barrier) { [weak self] in
+            self?.dependencies[key] = closure
+        }
     }
     
     func resolve<T>(type: T.Type) -> T? {
         let key = String(describing: T.self)
-        guard let closure = dependencies[key] else {
-            return nil
-        }
-        
-        return closure() as? T
+        return queue.sync {
+            guard let closure = dependencies[key] else {
+                return nil
+            }
+            return closure() as? T
+        }
     }
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @Cherrish-iOS/Cherrish-iOS/Core/DIContainer.swift around lines 20-25, The
dependencies dictionary is not thread-safe: mutations in register(type:closure:)
and reads in resolve can race; protect access to the private dependencies by
adding a synchronization primitive (e.g., a private serial DispatchQueue or an
NSLock) and wrap all reads and writes to dependencies with that lock/queue (use
sync for reads and async/sync or barrier for writes if using a concurrent
queue). Update register(type:closure:) and resolve to use this synchronization
so all accesses to dependencies are serialized and data races are prevented.

Comment on lines +1 to +2
//
// ContentView.swift
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the file header comment mismatch.

The header comment references "ContentView.swift" but the actual file is "TestView.swift". This inconsistency can cause confusion during code navigation and maintenance.

🔎 Proposed fix
 //
-//  ContentView.swift
+//  TestView.swift
 //  Cherrish-iOS
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//
// ContentView.swift
//
// TestView.swift
🤖 Prompt for AI Agents
In @Cherrish-iOS/Cherrish-iOS/Presentation/Feature/TestView.swift around lines
1-2, Update the file header comment to match the actual filename by replacing
the incorrect "ContentView.swift" text with "TestView.swift" in the top-of-file
comment; ensure any other header metadata (if present) also reflects
TestView.swift so the header is consistent with the file and class/struct names
in TestView.

Comment on lines +17 to +27
func assemble() {
preAssembler.assemble()

guard let testUseCase = DIContainer.shared.resolve(type: TestUseCase.self) else {
return
}

DIContainer.shared.register(type: TestViewModel.self) {
print("뷰모델 등록")
return TestViewModel(testUseCase: testUseCase)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove debug print statement before production.

The print statement on line 25 should be removed or replaced with proper logging infrastructure. Debug prints are not suitable for production code.

🔎 Proposed fix
 DIContainer.shared.register(type: TestViewModel.self) {
-    print("뷰모델 등록")
     return TestViewModel(testUseCase: testUseCase)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func assemble() {
preAssembler.assemble()
guard let testUseCase = DIContainer.shared.resolve(type: TestUseCase.self) else {
return
}
DIContainer.shared.register(type: TestViewModel.self) {
print("뷰모델 등록")
return TestViewModel(testUseCase: testUseCase)
}
func assemble() {
preAssembler.assemble()
guard let testUseCase = DIContainer.shared.resolve(type: TestUseCase.self) else {
return
}
DIContainer.shared.register(type: TestViewModel.self) {
return TestViewModel(testUseCase: testUseCase)
}
🤖 Prompt for AI Agents
In @Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift
around lines 17-27, In assemble(), remove the debug print statement inside the
DIContainer.shared.register closure (the line that prints "뷰모델 등록"); instead
either delete it or replace it with a call to the app logging facility (e.g.,
Logger.log or similar) if persistent logs are required; ensure the closure still
returns TestViewModel(testUseCase: testUseCase) and keep the guard resolving
TestUseCase unchanged.

Comment on lines +15 to +21
static func makeTestView() -> TestView {
guard let viewModel = DIContainer.shared.resolve(type: TestViewModel.self) else {
// TODO: DI 실패 시 기본으로 갈 곳 지정
fatalError()
}
return TestView(viewModel: viewModel)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace fatalError with graceful degradation.

Using fatalError() when DI resolution fails will crash the app. Since this factory is called during app initialization, a crash here provides a poor user experience. Consider:

  1. Logging the error for debugging
  2. Returning a default/fallback view
  3. Throwing an error that can be caught at the app level

This aligns with the TODO comment about specifying a default destination.

Would you like me to generate an implementation that provides graceful degradation with proper error logging?

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 17-17: TODOs should be resolved (DI 실패 시 기본으로 갈 곳 지정)

(todo)

🤖 Prompt for AI Agents
In @Cherrish-iOS/Cherrish-iOS/Presentation/ViewFactory.swift around lines 15-21,
Replace the fatalError in makeTestView: when DIContainer.shared.resolve(type:
TestViewModel.self) returns nil, log the failure (using the app logger or
print), and return a safe fallback TestView instance instead of crashing;
implement this by either creating a lightweight DefaultTestViewModel (or a
TestViewModel.mock()/initWithDefaults()) and returning TestView(viewModel:
defaultVM), or add a TestView.init() convenience initializer that creates an
empty/placeholder UI and return that; update makeTestView to use the fallback
path and keep the method signature unchanged so callers aren’t forced to handle
throws.

Copy link
Contributor

@sum130 sum130 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

끄아 어렵네요 이제 막 DI 이해했는데..
pr에도 설명 달아주셔서 감사합니두!! 찬찬히 읽어봤어요ㅎㅎ

Copy link
Contributor

@soseoyo12 soseoyo12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아따 어렵네요,,, 오늘안에 이해 완료 해보겠습니다

Copy link
Contributor

@wotjs020708 wotjs020708 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아직 어렵네요... 파이팅합시다! 고생하셨어욤

@y-eonee y-eonee merged commit b1ed994 into develop Jan 7, 2026
1 check passed
@y-eonee y-eonee deleted the setting/#7-dicontainer branch January 7, 2026 09:51
Kimgyuilli pushed a commit that referenced this pull request Jan 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Setting 프로젝트 세팅 시 사용 나연🐹

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Setting] DIContainer

4 participants