DI 프레임워크 비교: Needle vs Swinject vs WeaveDI
Swift를 위한 세 가지 주요 의존성 주입 프레임워크의 종합적인 비교입니다. 각 프레임워크의 장단점과 최적의 사용 사례를 학습하여 프로젝트에 적합한 선택을 하세요.
📊 빠른 비교 표
기능 | Needle | Swinject | WeaveDI |
---|---|---|---|
타입 안전성 | ✅ 컴파일 타임 | ⚠️ 런타임 | ✅ 컴파일 타임 + 런타임 |
성능 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
학습 곡선 | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
Swift Concurrency | ❌ 제한적 | ⚠️ 부분 지원 | ✅ 완전 지원 |
프로퍼티 래퍼 | ❌ 없음 | ⚠️ 제한적 | ✅ 고급 기능 |
코드 생성 | ✅ 필수 | ❌ 없음 | ⚠️ 선택적 |
번들 크기 영향 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
활발한 개발 | ⚠️ 느림 | ✅ 활발함 | ✅ 매우 활발함 |
🏗️ 아키텍처 철학
Needle: Uber의 계층적 접근 방식
Needle은 Dagger에서 영감을 받은 계층적 의존성 주입 패턴을 따릅니다. 컴파일 타임 코드 생성을 사용하여 의존성 그래프를 생성합니다.
// Needle's approach - Component hierarchy
protocol AppComponent: Component {
var userRepository: UserRepository { get }
var networkService: NetworkService { get }
}
class AppComponentImpl: AppComponent {
// Dependencies are provided through computed properties
var userRepository: UserRepository {
return UserRepositoryImpl(networkService: networkService)
}
var networkService: NetworkService {
return URLSessionNetworkService()
}
}
// Child components inherit from parent
protocol UserComponent: Component {
var appComponent: AppComponent { get }
var userViewController: UserViewController { get }
}
Needle의 작동 방식:
- 컴포넌트 계층 구조: 모든 의존성은 컴포넌트 트리 구조의 일부입니다
- 컴파일 타임 생성: 빌드 도구가 실제 구현 코드를 생성합니다
- 명시적 의존성: 모든 의존성 관계를 선언해야 합니다
- 타입 안전성: 모든 의존성 문제가 컴파일 타임에 포착됩니다
- 성능: 런타임 오버헤드가 전혀 없는 직접 메서드 호출
Needle의 장점:
- 최대 성능: 런타임 의존성 해결 오버헤드 없음
- 컴파일 타임 안전성: 프로덕션에서 누락된 의존성이 불가능함
- 대규모: 수백 개의 의존성을 가진 앱을 위해 설계됨
- 메모리 효율적: 최소한의 런타임 메모리 풋프린트
Needle의 단점:
- 가파른 학습 곡선: 복잡한 컴포넌트 계층 구조 개념
- 빌드 시간: 코드 생성이 상당한 빌드 시간을 추가함
- 유연성 부족: 런타임에 의존성 구성을 변경하기 어려움
- 보일러플레이트: 많은 프로토콜과 컴포넌트 정의가 필요함
Swinject: 컨테이너 기반 접근 방식
Swinject는 다른 언어의 인기 있는 DI 프레임워크와 유사한 컨테이너 기반 패턴을 사용합니다. 유연한 런타임 의존성 해결을 제공합니다.
// Swinject's approach - Container registration
let container = Container()
// Register dependencies with various scopes and configurations
container.register(NetworkService.self) { _ in
URLSessionNetworkService()
}.inObjectScope(.container) // Singleton scope
container.register(UserRepository.self) { resolver in
let networkService = resolver.resolve(NetworkService.self)!
return UserRepositoryImpl(networkService: networkService)
}
container.register(UserViewController.self) { resolver in
let userRepository = resolver.resolve(UserRepository.self)!
return UserViewController(userRepository: userRepository)
}
// Resolve dependencies manually
let userViewController = container.resolve(UserViewController.self)!
Swinject의 작동 방식:
- 중앙 컨테이너: 하나의 컨테이너가 모든 서비스 등록을 관리합니다
- 런타임 해결: 요청 시 의존성이 해결됩니다
- 유연한 스코핑: 객체 생명주기 제어 (싱글톤, 일시적 등)
- 수동 등록: 모든 서비스를 명시적으로 등록합니다
- 리플렉션 기반: 타입 해결을 위해 Swift의 리플렉션 기능 사용
Swinject의 장점:
- 최대 유연성: 런타임에 의존성 구성 변경 가능
- 풍부한 기능: 광범위한 구성 옵션과 생명주기 관리
- 성숙한 에코시스템: 대규모 커뮤니티와 광범위한 문서
- 쉬운 테스팅: 의존성을 mock으로 간단하게 교체
Swinject의 단점:
- 런타임 에러: 의존성 문제가 런타임에만 발견됨
- 성능 오버헤드: 리플렉션과 컨테이너 조회 비용
- 메모리 사용량: 컨테이너가 모든 등록에 대한 참조를 유지함
- 수동 작업: 모든 의존성을 명시적으로 등록해야 함
WeaveDI: 모던 Swift 우선 접근 방식
WeaveDI는 동시성, 프로퍼티 래퍼, 타입 안전성을 포함한 Swift의 최신 기능을 수용하는 모던 Swift 우선 설계로 두 세계의 장점을 결합합니다.
// WeaveDI's approach - Property wrapper magic with modern Swift
class UserViewController: UIViewController {
// Simple, clean dependency injection with property wrappers
@Injected var userRepository: UserRepository?
@Injected var logger: LoggerProtocol?
// Factory pattern for stateless services
@Factory var imageProcessor: ImageProcessor
// Note: @SafeInject deprecated in 3.2.0, use @Injected with error handling instead
override func viewDidLoad() {
super.viewDidLoad()
// Dependencies are automatically resolved and injected
guard let repository = userRepository else {
logger?.error("UserRepository not available")
return
}
// Use the injected dependencies naturally
loadUserData(using: repository)
}
}
// Registration is simple and clean
await WeaveDI.Container.bootstrap { container in
container.register(NetworkService.self) {
URLSessionNetworkService()
}
container.register(UserRepository.self) {
UserRepositoryImpl() // Dependencies auto-injected
}
container.register(LoggerProtocol.self) {
ConsoleLogger()
}
}
실제 WeaveDI 튜토리얼 코드 예제
다음은 WeaveDI의 튜토리얼 리소스에서 가져온 실제 코드 예제입니다:
🎯 WeaveDI 소개 튜토리얼 코드
// Example from Tutorial-MeetWeaveDI-01-01.swift - AutoOptimization Features
import WeaveDI
import LogMacro
enum AutoOptimizationShowcase {
static func printOverview() {
// Sample data generation: Simple type registration/resolution
struct ServiceA: Sendable {}
struct ServiceB: Sendable {}
_ = UnifiedDI.register(ServiceA.self) { ServiceA() }
_ = UnifiedDI.register(ServiceB.self) { ServiceB() }
for _ in 0..<5 { _ = UnifiedDI.resolve(ServiceA.self) }
for _ in 0..<3 { _ = UnifiedDI.resolve(ServiceB.self) }
let stats = UnifiedDI.stats()
let graph = UnifiedDI.autoGraph()
let optimized = UnifiedDI.optimizedTypes()
#logInfo("📊 [AutoDI] Stats: \(stats)")
#logInfo("🗺️ [AutoDI] Graph:\n\(graph)")
#logInfo("⚡ [AutoDI] Optimized: \(optimized)")
}
}
🏗️ 중급 WeaveDI 튜토리얼 코드
// Example from Tutorial-IntermediateWeaveDI-01-01.swift - Core Usage
import WeaveDI
import Foundation
// MARK: Sample Domain
protocol UserRepository: Sendable { func fetchName(id: String) -> String }
struct UserRepositoryImpl: UserRepository, Sendable {
func fetchName(id: String) -> String { "user-\(id)" }
}
protocol UserUseCase: Sendable { func greet(id: String) -> String }
struct UserUseCaseImpl: UserUseCase, Sendable {
let repo: UserRepository
func greet(id: String) -> String { "Hello, \(repo.fetchName(id: id))" }
}
// MARK: Option A) UnifiedDI (Clean API)
func exampleRegisterAndResolve_UnifiedDI() {
// 1) Registration (immediate instance creation)
_ = UnifiedDI.register(UserRepository.self) { UserRepositoryImpl() }
_ = UnifiedDI.register(UserUseCase.self) {
// Dependencies safely resolved when needed
let repo = UnifiedDI.resolve(UserRepository.self) ?? UserRepositoryImpl()
return UserUseCaseImpl(repo: repo)
}
// 2) Resolution (usage)
let useCase = UnifiedDI.resolve(UserUseCase.self)
_ = useCase?.greet(id: "42")
}
// MARK: Option B) WeaveDI.Container.live (Explicit container)
func exampleRegisterAndResolve_WeaveDI.Container() {
// 1) Registration (immediate instance registration)
let repo = WeaveDI.Container.live.register(UserRepository.self) { UserRepositoryImpl() }
WeaveDI.Container.live.register(UserUseCase.self, instance: UserUseCaseImpl(repo: repo))
// 2) Resolution
let useCase = WeaveDI.Container.live.resolve(UserUseCase.self)
_ = useCase?.greet(id: "7")
}
// MARK: Bootstrap Example (Bulk registration at app start)
func exampleBootstrap() async {
await WeaveDI.Container.bootstrap { container in
_ = container.register(UserRepository.self) { UserRepositoryImpl() }
_ = container.register(UserUseCase.self) {
let repo = container.resolveOrDefault(UserRepository.self, default: UserRepositoryImpl())
return UserUseCaseImpl(repo: repo)
}
}
}
🚀 고급 WeaveDI vs Needle 스타일
// Example from Tutorial-NeedleStyle-01-01.swift - Framework Comparison
import WeaveDI
/*
🏆 WeaveDI advantages over Needle:
✅ Compile-time safety: Equivalent (macros vs code generation)
🚀 Runtime performance: WeaveDI wins (zero cost + Actor optimization)
🎯 Swift 6 support: WeaveDI exclusive (perfect native support)
🛠️ Code generation: WeaveDI wins (optional vs required)
📚 Learning curve: WeaveDI wins (gradual vs steep)
🔄 Migration: WeaveDI wins (gradual vs All-or-nothing)
*/
// Needle's complex Component definition vs WeaveDI's simple approach
extension UnifiedDI {
static func setupApp() {
// Much simpler and more intuitive!
_ = register(LoggerProtocol.self) { ConsoleLogger() }
_ = register(NetworkServiceProtocol.self) { NetworkServiceImpl() }
_ = register(UserServiceProtocol.self) { UserServiceImpl() }
// Enable Needle-level performance
enableStaticOptimization()
// Compile-time verification (equivalent safety to Needle)
validateNeedleStyle(
component: AppComponent.self,
dependencies: [LoggerProtocol.self, NetworkServiceProtocol.self, UserServiceProtocol.self]
)
}
}
🔧 실제 서비스 구현 예제
Here are more practical examples from WeaveDI's actual tutorial resources:
📝 세션 관리가 있는 로깅 서비스
// Example from Tutorial-MeetWeaveDI-02-01.swift - LoggingService
import Foundation
import LogMacro
protocol LoggingService: Sendable {
var sessionId: String { get }
func logAction(_ action: String)
func logInfo(_ message: String)
}
final class DefaultLoggingService: LoggingService {
let sessionId: String
init() {
// Generate new session ID each time (Factory pattern essence!)
self.sessionId = UUID().uuidString.prefix(8).uppercased().description
#logInfo("📝 [LoggingService] New session started: \(sessionId)")
}
func logAction(_ action: String) {
#logInfo("📝 [\(sessionId)] ACTION: \(action)")
}
func logInfo(_ message: String) {
#logInfo("📝 [\(sessionId)] INFO: \(message)")
}
}
🌐 에러 처리가 있는 네트워크 서비스
// Example from Tutorial-MeetWeaveDI-03-01.swift - NetworkService
import Foundation
protocol NetworkService: Sendable {
var isConnected: Bool { get }
func checkConnection() async -> Bool
func uploadData(_ data: String) async throws -> String
}
final class DefaultNetworkService: NetworkService {
private var _isConnected = false
var isConnected: Bool {
return _isConnected
}
func checkConnection() async -> Bool {
print("🌐 [NetworkService] Checking network connection...")
// Simulate network check with delay
try? await Task.sleep(nanoseconds: 1_000_000_000) // 1 second wait
// Randomly determine connection status (simulate failures)
_isConnected = Bool.random()
print("🌐 [NetworkService] Connection status: \(_isConnected ? "Connected" : "Failed")")
return _isConnected
}
func uploadData(_ data: String) async throws -> String {
guard isConnected else {
throw NetworkError.notConnected
}
print("🌐 [NetworkService] Uploading data: \(data)")
try? await Task.sleep(nanoseconds: 500_000_000) // 0.5 second wait
let result = "Upload successful: \(data) (\(Date().timeIntervalSince1970))"
print("🌐 [NetworkService] \(result)")
return result
}
}
enum NetworkError: Error, LocalizedError {
case notConnected
case uploadFailed
var errorDescription: String? {
switch self {
case .notConnected:
return "Not connected to network"
case .uploadFailed:
return "Data upload failed"
}
}
}
🏗️ 클린 아키텍처 Repository 패턴
// Example from Tutorial-MeetWeaveDI-04-01.swift - Repository Pattern
import Foundation
/// Repository protocol for data storage abstraction
protocol CounterRepository: Sendable {
func getCurrentCount() async -> Int
func saveCount(_ count: Int) async
func getCountHistory() async -> [CounterHistory]
}
/// Repository implementation using UserDefaults
final class UserDefaultsCounterRepository: CounterRepository {
private let userDefaults = UserDefaults.standard
private let countKey = "saved_counter_value"
private let historyKey = "counter_history"
func getCurrentCount() async -> Int {
let count = userDefaults.integer(forKey: countKey)
print("💾 [Repository] Loading saved count: \(count)")
return count
}
func saveCount(_ count: Int) async {
userDefaults.set(count, forKey: countKey)
// Add to history as well
var history = await getCountHistory()
let newEntry = CounterHistory(
count: count,
timestamp: Date(),
action: count > (history.last?.count ?? 0) ? "Increase" : "Decrease"
)
history.append(newEntry)
// Keep only recent 10 entries
if history.count > 10 {
history = Array(history.suffix(10))
}
if let encoded = try? JSONEncoder().encode(history) {
userDefaults.set(encoded, forKey: historyKey)
}
print("💾 [Repository] Count saved: \(count)")
}
func getCountHistory() async -> [CounterHistory] {
guard let data = userDefaults.data(forKey: historyKey),
let history = try? JSONDecoder().decode([CounterHistory].self, from: data) else {
return []
}
return history
}
}
struct CounterHistory: Codable, Sendable {
let count: Int
let timestamp: Date
let action: String
var formattedTime: String {
let formatter = DateFormatter()
formatter.timeStyle = .short
return formatter.string(from: timestamp)
}
}
🚀 고급 Actor 최적화 예제
// Example from Tutorial-AdvancedWeaveDI-02-01.swift - Actor Hop Metrics
import Foundation
import WeaveDI
import LogMacro
enum ActorHopMetrics {
static func collect() async {
// Sample type registration (for parallel resolution)
struct SessionStore: Sendable { let id = UUID() }
_ = UnifiedDI.register(SessionStore.self) { SessionStore() }
// Parallel resolution testing
await withTaskGroup(of: Void.self) { group in
for _ in 0..<10 {
group.addTask {
_ = UnifiedDI.resolve(SessionStore.self)
}
}
}
let hopStats = await UnifiedDI.actorHopStats
let suggestions = await UnifiedDI.actorOptimizations
#logInfo("🎯 [Actor] HopStats: \(hopStats)")
#logInfo("💡 [Actor] Suggestions: \(suggestions)")
}
}
⚙️ 환경별 구성
// Example from Tutorial-IntermediateWeaveDI-02-01.swift - Environment Config
import WeaveDI
import Foundation
protocol APIClient: Sendable { var baseURL: String { get } }
struct DevAPI: APIClient, Sendable { let baseURL = "https://dev.example.com" }
struct ProdAPI: APIClient, Sendable { let baseURL = "https://api.example.com" }
func exampleEnvironmentConfig(isProd: Bool) async {
// 1) Bootstrap registration at app start
await WeaveDI.Container.bootstrap { c in
if isProd {
_ = c.register(APIClient.self) { ProdAPI() }
} else {
_ = c.register(APIClient.self) { DevAPI() }
}
}
// 2) Resolution and usage
let client = DI.resolve(APIClient.self)
_ = client?.baseURL // Environment-appropriate baseURL
}
📱 CountApp을 사용한 완전한 SwiftUI 앱 예제
Here's a complete real-world application example from WeaveDI's tutorial resources:
카운터 앱 구현
// Example from Tutorial-MeetWeaveDI-01-01.swift - Simple Counter
import SwiftUI
struct ContentView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
Text("WeaveDI Counter")
.font(.largeTitle)
.fontWeight(.bold)
Text("\(count)")
.font(.system(size: 60, weight: .bold))
.foregroundColor(.blue)
HStack(spacing: 20) {
Button("-") {
count -= 1
}
.font(.title)
.frame(width: 50, height: 50)
.background(Color.red)
.foregroundColor(.white)
.clipShape(Circle())
Button("+") {
count += 1
}
.font(.title)
.frame(width: 50, height: 50)
.background(Color.green)
.foregroundColor(.white)
.clipShape(Circle())
}
}
.padding()
}
}
#Preview {
ContentView()
}
의존성 주입이 있는 완전한 데모 앱
// Example from WeaveDI-GettingStarted-Complete.swift - Full Demo App
import Foundation
import WeaveDI
import SwiftUI
// MARK: - 1. Service Definitions
protocol GreetingService: Sendable {
func greet(name: String) -> String
func farewell(name: String) -> String
}
final class SimpleGreetingService: GreetingService {
func greet(name: String) -> String {
return "Hello, \(name)!"
}
func farewell(name: String) -> String {
return "Goodbye, \(name)!"
}
}
protocol LoggingService: Sendable {
func log(message: String)
}
final class ConsoleLoggingService: LoggingService {
func log(message: String) {
print("📝 Log: \(message)")
}
}
protocol ConfigService: Sendable {
var appName: String { get }
var version: String { get }
}
final class DefaultConfigService: ConfigService {
let appName = "WeaveDI Demo"
let version = "1.0.0"
}
// MARK: - 2. Service Registration & Bootstrap
extension WeaveDI.Container {
static func setupDependencies() async {
// Synchronous bootstrap registering all services
await WeaveDI.Container.bootstrap { container in
// Register greeting service
container.register(GreetingService.self) {
SimpleGreetingService()
}
// Register logging service
container.register(LoggingService.self) {
ConsoleLoggingService()
}
// Register config service
container.register(ConfigService.self) {
DefaultConfigService()
}
}
}
}
// MARK: - 3. Property Wrapper Dependency Injection
final class WelcomeController: Sendable {
// @Injected for dependency injection (optional)
@Injected private var greetingService: GreetingService?
@Injected private var loggingService: LoggingService?
func welcomeUser(name: String) -> String {
guard let service = greetingService else {
return "Service unavailable"
}
let message = service.greet(name: name)
loggingService?.log(message: "User \(name) welcome processed")
return message
}
func farewellUser(name: String) -> String {
guard let service = greetingService else {
return "Service unavailable"
}
let message = service.farewell(name: name)
loggingService?.log(message: "User \(name) farewell processed")
return message
}
}
// MARK: - 4. SwiftUI App Integration
@main
struct WeaveDIDemoApp: App {
init() {
// Setup dependencies on app start
Task {
await WeaveDI.Container.setupDependencies()
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@Injected private var greetingService: GreetingService?
@Injected private var loggingService: LoggingService?
@Injected private var configService: ConfigService?
@State private var userName = ""
@State private var message = ""
@State private var isGreeting = true
var body: some View {
VStack(spacing: 20) {
// App info
Text(configService?.appName ?? "No App Name")
.font(.largeTitle)
.fontWeight(.bold)
Text("Version: \(configService?.version ?? "Unknown")")
.font(.subheadline)
.foregroundColor(.secondary)
// User input
TextField("Enter your name", text: $userName)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
// Action selection
Picker("Action", selection: $isGreeting) {
Text("Greet").tag(true)
Text("Farewell").tag(false)
}
.pickerStyle(SegmentedPickerStyle())
.padding()
// Execute button
Button(isGreeting ? "Greet" : "Farewell") {
processAction()
}
.disabled(userName.isEmpty)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
// Result display
Text(message)
.foregroundColor(.primary)
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
}
.padding()
}
private func processAction() {
guard let service = greetingService else {
message = "Service unavailable"
loggingService?.log(message: "Service usage failed")
return
}
message = isGreeting
? service.greet(name: userName)
: service.farewell(name: userName)
loggingService?.log(message: "User action processed: \(isGreeting ? "greet" : "farewell")")
}
}
// MARK: - 5. Business Logic Example
final class BusinessLogic: Sendable {
@Injected private var greetingService: GreetingService?
@Injected private var loggingService: LoggingService?
func processWelcome(userName: String) -> String {
let message = greetingService?.greet(name: userName) ?? "Service unavailable"
loggingService?.log(message: "User \(userName) welcome processed")
return message
}
func processFarewell(userName: String) -> String {
let message = greetingService?.farewell(name: userName) ?? "Service unavailable"
loggingService?.log(message: "User \(userName) farewell processed")
return message
}
}
// MARK: - Usage Example
func exampleUsage() async {
// 1. Setup dependencies
await WeaveDI.Container.setupDependencies()
// 2. Direct resolution
let service = UnifiedDI.resolve(GreetingService.self)
let directMessage = service?.greet(name: "Direct User") ?? "No service"
print("Direct resolution: \(directMessage)")
// 3. Through controller usage
let controller = WelcomeController()
let controllerMessage = controller.welcomeUser(name: "Controller User")
print("Controller usage: \(controllerMessage)")
// 4. Business logic usage
let businessLogic = BusinessLogic()
let businessMessage = businessLogic.processWelcome(userName: "Business User")
print("Business logic: \(businessMessage)")
}
🧪 엔터프라이즈급 테스팅 예제
WeaveDI includes comprehensive testing patterns for production apps:
// Example from Tutorial-MeetWeaveDI-06-03.swift - ModuleFactory Testing
import Foundation
import XCTest
import WeaveDI
final class ModuleFactoryTests: XCTestCase {
override func setUp() {
super.setUp()
// Initialize container and optimization system before tests
WeaveDI.Container.shared.removeAll()
AutoDIOptimizer.shared.reset()
}
func test_complete_app_bootstrap_scenario() async throws {
// Given: Real app startup scenario
let optimizer = AutoDIOptimizer.shared
// Step 1: Optimizer setup
optimizer.setOptimizationEnabled(true)
optimizer.setLogLevel(.errors)
optimizer.setDebounceInterval(ms: 100)
// Step 2: AppWeaveDI.Container bootstrap
let appContainer = AppWeaveDI.Container.shared
await appContainer.registerDefaultDependencies()
// Step 3: Registration status monitoring
await appContainer.monitorRegistrationStatus()
// Step 4: Health check
let isHealthy = await appContainer.performHealthCheck()
// Then: All steps should succeed
XCTAssertTrue(isHealthy, "System should be healthy after bootstrap")
let stats = optimizer.getStats()
XCTAssertGreaterThan(stats.registered, 5, "At least 5 types should be registered")
}
func test_concurrent_dependency_resolution() async throws {
// Given: ModuleFactory pattern setup
var manager = ModuleFactoryManager()
manager.registerDefaultDependencies()
await manager.registerAll(to: WeaveDI.Container.shared)
// When: Concurrent dependency resolution from multiple Tasks
let tasks = (0..<10).map { index in
Task {
for _ in 0..<20 {
let counterService = UnifiedDI.resolve(CounterService.self)
let loggingService = UnifiedDI.resolve(LoggingService.self)
let repository = UnifiedDI.resolve(CounterRepository.self)
let useCase = UnifiedDI.resolve(CounterUseCase.self)
XCTAssertNotNil(counterService)
XCTAssertNotNil(loggingService)
XCTAssertNotNil(repository)
XCTAssertNotNil(useCase)
}
return index
}
}
// Then: All Tasks should complete successfully
let results = await withTaskGroup(of: Int.self) { group in
for task in tasks {
group.addTask { await task.value }
}
var completedTasks: [Int] = []
for await result in group {
completedTasks.append(result)
}
return completedTasks
}
XCTAssertEqual(results.count, 10, "All concurrent tasks should complete")
XCTAssertEqual(Set(results).count, 10, "All Tasks should return unique results")
}
}
WeaveDI의 작동 방식:
- 프로퍼티 래퍼:
@Injected
,@Factory
가 의존성 주입을 처리합니다 (@Inject
와@SafeInject
는 3.2.0에서 deprecated) - 자동 해결: 접근 시 자동으로 의존성이 주입됩니다
- 타입 안전 레지스트리: 런타임 유연성과 함께 컴파일 타임 타입 체킹
- Swift Concurrency: async/await 및 actor에 대한 내장 지원
- 스마트 최적화: 자동 성능 최적화 및 캐싱
WeaveDI의 장점:
- 개발자 경험: 최소한의 보일러플레이트로 깔끔하고 직관적인 API
- 성능: 런타임 유연성과 함께 컴파일 타임에 가까운 속도
- 모던 Swift: Swift concurrency 및 프로퍼티 래퍼 완전 지원
- 타입 안전성: 컴파일 타임 체킹으로 대부분의 의존성 에러 방지
- 유연성: 재빌드 없이 구성 변경이 쉬움
WeaveDI의 단점:
- 새로운 프레임워크: Swinject에 비해 작은 커뮤니티
- Swift 5.5+ 필요: 이전 Swift 버전에서는 사용 불가
- 프로퍼티 래퍼 학습: 팀이 프로퍼티 래퍼 개념을 이해해야 함
🚀 성능 분석
런타임 성능 비교
// Performance test: Resolving 1000 dependencies
// Results on iPhone 14 Pro, Release build
// Needle Performance
func benchmarkNeedle() {
let startTime = CFAbsoluteTimeGetCurrent()
for _ in 0..<1000 {
let component = AppComponentImpl()
let service = component.userService // Direct property access
_ = service.getData()
}
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print("Needle: \(timeElapsed)s") // ~0.001s total
}
// Swinject Performance
func benchmarkSwinject() {
let container = Container()
setupSwinjectContainer(container)
let startTime = CFAbsoluteTimeGetCurrent()
for _ in 0..<1000 {
let service = container.resolve(UserService.self)! // Container lookup
_ = service.getData()
}
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print("Swinject: \(timeElapsed)s") // ~0.15s total
}
// WeaveDI Performance
func benchmarkWeaveDI() {
await setupWeaveDI()
let startTime = CFAbsoluteTimeGetCurrent()
for _ in 0..<1000 {
let service = UnifiedDI.resolve(UserService.self) // Optimized resolution
_ = service.getData()
}
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print("WeaveDI: \(timeElapsed)s") // ~0.002s total
}
성능 결과:
- Needle: ~0.001ms per resolution (fastest, but least flexible)
- WeaveDI: ~0.002ms per resolution (nearly as fast, much more flexible)
- Swinject: ~0.15ms per resolution (slower, but most mature)
메모리 사용량 분석
// Memory footprint for typical iOS app with 50 registered services
// Needle Memory Usage
class NeedleMemoryAnalysis {
static func analyze() {
// Needle generates static code, minimal runtime overhead
let componentMemory = MemoryLayout<AppComponentImpl>.size // ~64 bytes
let totalServices = 50
let estimatedMemory = componentMemory * totalServices / 1024
print("Needle memory overhead: ~\(estimatedMemory)KB")
// Result: ~2-3KB total overhead
}
}
// Swinject Memory Usage
class SwinjectMemoryAnalysis {
static func analyze() {
let container = Container()
// Container maintains registry of all services
let containerOverhead = MemoryLayout<Container>.size // ~512 bytes
let serviceRegistrations = 50 * 128 // Each registration ~128 bytes
let reflectionMetadata = 50 * 256 // Reflection info per service
let totalMemory = (containerOverhead + serviceRegistrations + reflectionMetadata) / 1024
print("Swinject memory overhead: ~\(totalMemory)KB")
// Result: ~45-50KB total overhead
}
}
// WeaveDI Memory Usage
class WeaveDIMemoryAnalysis {
static func analyze() {
// Optimized registry with smart caching
let registryOverhead = MemoryLayout<WeaveDI.Container>.size // ~256 bytes
let serviceMetadata = 50 * 64 // Minimal metadata per service
let cacheMemory = 50 * 32 // Lightweight cache entries
let totalMemory = (registryOverhead + serviceMetadata + cacheMemory) / 1024
print("WeaveDI memory overhead: ~\(totalMemory)KB")
// Result: ~8-10KB total overhead
}
}
빌드 시간 영향
// Build time analysis for medium-sized project (100 services, 200 files)
// Needle Build Impact
/*
Additional build steps:
1. Dependency graph analysis: 15-30s
2. Code generation: 20-40s
3. Generated code compilation: 15-25s
Total additional build time: 50-95s
Impact: Significant, especially during development
*/
// Swinject Build Impact
/*
Additional build steps:
1. Framework compilation: 3-5s
Total additional build time: 3-5s
Impact: Minimal
*/
// WeaveDI Build Impact
/*
Additional build steps:
1. Optional macro expansion: 5-8s
2. Framework compilation: 2-3s
Total additional build time: 7-11s
Impact: Low
*/
🎯 상세 사용 사례 분석
Needle을 선택해야 하는 경우
✅ 대규모 엔터프라이즈 애플리케이션에 완벽
// Example: Banking application with strict performance requirements
protocol BankingAppComponent: Component {
// Needle excels with large, complex dependency hierarchies
var authenticationModule: AuthenticationModule { get }
var transactionModule: TransactionModule { get }
var fraudDetectionModule: FraudDetectionModule { get }
var complianceModule: ComplianceModule { get }
var reportingModule: ReportingModule { get }
}
class BankingAppComponentImpl: BankingAppComponent {
// Compile-time safety ensures all critical services are available
var authenticationModule: AuthenticationModule {
// Complex dependency chains resolved at compile time
return AuthenticationModuleImpl(
biometricService: biometricService,
tokenService: tokenService,
cryptoService: cryptoService
)
}
// Performance critical - zero runtime overhead
var transactionModule: TransactionModule {
return TransactionModuleImpl(
validator: transactionValidator,
processor: transactionProcessor,
logger: auditLogger
)
}
}
// Child components for feature modules
protocol TransactionFeatureComponent: Component {
var parent: BankingAppComponent { get }
var transactionViewController: TransactionViewController { get }
}
Needle 최적 사용 사례:
- High-frequency trading apps: Where microsecond performance matters
- Banking/Finance: Where runtime failures are unacceptable
- Large enterprise apps: With hundreds of interconnected services
- Gaming engines: Where performance is absolutely critical
❌ Needle을 피해야 하는 경우:
- Small to medium projects (complexity overhead not justified)
- Rapid prototyping (code generation slows iteration)
- Dynamic service configuration needs
- Teams new to dependency injection
Swinject를 선택해야 하는 경우
✅ 최대 유연성 시나리오에 완벽
// Example: Multi-tenant SaaS application with runtime configuration
class SaaSDependencyManager {
private let container = Container()
func setupForTenant(_ tenantConfig: TenantConfiguration) {
// Swinject excels at runtime reconfiguration
// Different database per tenant
container.register(DatabaseService.self) { _ in
switch tenantConfig.databaseType {
case .postgresql:
return PostgreSQLService(config: tenantConfig.dbConfig)
case .mongodb:
return MongoDBService(config: tenantConfig.dbConfig)
case .sqlite:
return SQLiteService(config: tenantConfig.dbConfig)
}
}.inObjectScope(.container)
// Different payment processors per region
container.register(PaymentService.self) { _ in
switch tenantConfig.region {
case .northAmerica:
return StripePaymentService()
case .europe:
return AdyenPaymentService()
case .asia:
return AliPayService()
}
}
// Complex object graphs with circular dependencies
container.register(UserService.self) { resolver in
UserService(
database: resolver.resolve(DatabaseService.self)!,
payment: resolver.resolve(PaymentService.self)!,
notification: resolver.resolve(NotificationService.self)!
)
}
container.register(NotificationService.self) { resolver in
NotificationService(
userService: resolver.resolve(UserService.self)!
)
}
}
// Advanced scoping and lifecycle management
func setupAdvancedScoping() {
// Per-request scoping for web services
container.register(RequestContext.self) { _ in
RequestContext()
}.inObjectScope(.graph)
// Singleton with initialization callback
container.register(AnalyticsService.self) { _ in
GoogleAnalyticsService()
}.inObjectScope(.container)
.initCompleted { _, service in
service.configure(apiKey: ConfigManager.analyticsKey)
}
}
}
Swinject 최적 사용 사례:
- Multi-tenant applications: Different configurations per tenant
- A/B testing platforms: Runtime switching of service implementations
- Plugin architectures: Dynamic loading of service modules
- Legacy integration: Working with Objective-C heavy codebases
- Complex object graphs: Circular dependencies and advanced scoping
❌ Swinject를 피해야 하는 경우:
- Performance-critical applications
- Memory-constrained environments
- Projects prioritizing type safety over flexibility
WeaveDI를 선택해야 하는 경우
✅ 모던 Swift 개발에 완벽
// Example: Modern iOS app with SwiftUI and Swift Concurrency
@main
struct WeatherApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.task {
// WeaveDI excels with modern Swift patterns
await setupDependencies()
}
}
}
@DIActor
func setupDependencies() async {
// Clean, modern async bootstrap
await WeaveDI.Container.bootstrap { container in
// Simple registration with auto-injection
container.register(LocationService.self) {
CoreLocationService()
}
container.register(WeatherAPIService.self) {
OpenWeatherMapService() // LocationService auto-injected
}
container.register(WeatherRepository.self) {
WeatherRepositoryImpl() // All dependencies auto-injected
}
}
}
}
// SwiftUI Views with clean dependency injection
struct WeatherView: View {
@Injected var weatherRepository: WeatherRepository?
@Injected var locationService: LocationService?
@Factory var weatherViewModel: WeatherViewModel
@Injected var logger: LoggerProtocol?
@State private var weather: Weather?
@State private var isLoading = false
var body: some View {
VStack {
if isLoading {
ProgressView("Loading weather...")
} else if let weather = weather {
WeatherDisplayView(weather: weather)
} else {
Text("Unable to load weather")
}
}
.task {
await loadWeather()
}
.refreshable {
await loadWeather()
}
}
private func loadWeather() async {
// Dependencies automatically injected and available
guard let repository = weatherRepository,
let location = locationService else {
logger?.error("Required services not available")
return
}
isLoading = true
do {
// Modern Swift concurrency with injected services
let currentLocation = try await location.getCurrentLocation()
let weatherData = try await repository.getWeather(for: currentLocation)
await MainActor.run {
self.weather = weatherData
self.isLoading = false
}
} catch {
logger?.error("Failed to load weather: \(error)")
await MainActor.run {
self.isLoading = false
}
}
}
}
// ViewModels with sophisticated dependency management
@MainActor
class WeatherViewModel: ObservableObject {
@Published var weather: Weather?
@Published var forecast: [Weather] = []
@Published var isLoading = false
// Multiple injection patterns for different needs
@Injected var repository: WeatherRepository?
@Factory var dateFormatter: DateFormatter // New instance each time
@Injected var analytics: AnalyticsService? // Use @Injected (3.2.0+)
func loadForecast() async {
// Clean async/await usage with injected dependencies
guard let repo = repository else { return }
isLoading = true
do {
let forecastData = try await repo.getForecast()
self.forecast = forecastData
// Analytics tracking with error-safe injection
analytics?.track("forecast_loaded", parameters: [
"items_count": forecastData.count
])
} catch {
analytics?.track("forecast_error", parameters: [
"error": error.localizedDescription
])
}
isLoading = false
}
}
WeaveDI 최적 사용 사례:
- Modern iOS/macOS apps: Built with SwiftUI and Swift Concurrency
- Rapid development: Quick prototyping and iteration
- Clean architecture: MVVM, Clean Architecture, or similar patterns
- Testing-focused: Easy mocking and test isolation
- Developer experience priority: Teams valuing clean, readable code
❌ WeaveDI를 피해야 하는 경우:
- Objective-C heavy projects
- Projects that cannot adopt Swift 5.5+
- Teams unfamiliar with property wrapper concepts
🔄 마이그레이션 전략
Swinject에서 WeaveDI로 마이그레이션
단계 1: 준비
// Current Swinject code
class SwinjectUserService {
private let container: Container
init(container: Container) {
self.container = container
}
func createViewController() -> UserViewController {
let userRepository = container.resolve(UserRepository.self)!
let logger = container.resolve(LoggerProtocol.self)!
let analytics = container.resolve(AnalyticsService.self)!
return UserViewController(
userRepository: userRepository,
logger: logger,
analytics: analytics
)
}
}
// Step 1: Identify all manual resolver.resolve() calls
// Step 2: List all constructor injection patterns
// Step 3: Catalog all container.register() calls
단계 2: 점진적 마이그레이션
// Migrate ViewControllers first (easiest wins)
class UserViewController: UIViewController {
// Replace constructor injection with property wrappers
@Injected var userRepository: UserRepository?
@Injected var logger: LoggerProtocol?
@Injected var analytics: AnalyticsService?
// Remove complex constructor
override init(nibName: String?, bundle: Bundle?) {
super.init(nibName: nibName, bundle: bundle)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
// Dependencies automatically available
guard let repository = userRepository else {
logger?.error("UserRepository not available")
return
}
// Use services naturally
loadUserData(using: repository)
}
}
// Migrate registration (run both systems in parallel during transition)
func setupMigrationPhase() async {
// Keep Swinject running for unmigrated code
let swinjectContainer = Container()
setupSwinjectRegistrations(swinjectContainer)
// Add WeaveDI for new code
await WeaveDI.Container.bootstrap { container in
// Mirror Swinject registrations in WeaveDI
container.register(UserRepository.self) {
UserRepositoryImpl()
}
container.register(LoggerProtocol.self) {
ConsoleLogger()
}
container.register(AnalyticsService.self) {
FirebaseAnalyticsService()
}
}
}
Needle에서 WeaveDI로 마이그레이션
단계 1: 컴포넌트 분석
// Current Needle component
protocol UserFeatureComponent: Component {
var userRepository: UserRepository { get }
var userService: UserService { get }
var userViewController: UserViewController { get }
}
class UserFeatureComponentImpl: UserFeatureComponent {
var userRepository: UserRepository {
return UserRepositoryImpl(
networkService: networkService,
cacheService: cacheService
)
}
var userService: UserService {
return UserServiceImpl(repository: userRepository)
}
var userViewController: UserViewController {
return UserViewController(userService: userService)
}
}
// Step 1: Map component dependencies to simple registrations
// Step 2: Identify constructor injection patterns
// Step 3: Plan property wrapper migration
단계 2: 단순화
// Replace complex component hierarchy with simple registration
await WeaveDI.Container.bootstrap { container in
// Mirror component dependencies as simple registrations
container.register(NetworkService.self) {
URLSessionNetworkService()
}
container.register(CacheService.self) {
CoreDataCacheService()
}
container.register(UserRepository.self) {
UserRepositoryImpl() // Dependencies auto-injected
}
container.register(UserService.self) {
UserServiceImpl() // Dependencies auto-injected
}
}
// Replace component-based injection with property wrappers
class UserViewController: UIViewController {
@Injected var userService: UserService?
// Much simpler than component hierarchy
override func viewDidLoad() {
super.viewDidLoad()
// Service automatically injected
userService?.loadUserData()
}
}
📊 결정 매트릭스
프로젝트에 적합한 프레임워크를 선택하기 위해 이 결정 매트릭스를 사용하세요:
Criteria | Weight | Needle Score | Swinject Score | WeaveDI Score |
---|---|---|---|---|
Performance | 🔥🔥🔥 | 10 | 6 | 9 |
Type Safety | 🔥🔥🔥 | 10 | 4 | 9 |
Developer Experience | 🔥🔥🔥 | 5 | 7 | 10 |
Learning Curve | 🔥🔥 | 3 | 8 | 9 |
Flexibility | 🔥🔥 | 4 | 10 | 8 |
Swift Concurrency | 🔥🔥🔥 | 2 | 5 | 10 |
Community | 🔥 | 7 | 9 | 6 |
Build Time | 🔥🔥 | 4 | 9 | 8 |
가중 점수:
- Needle: 6.8/10 (Best for performance-critical enterprise apps)
- Swinject: 7.2/10 (Best for maximum flexibility needs)
- WeaveDI: 8.9/10 (Best for modern Swift development)
🏆 최종 권장사항
다음의 경우 WeaveDI를 선택하세요:
- ✅ Building modern Swift applications (iOS 13+, Swift 5.5+)
- ✅ Using SwiftUI and Swift Concurrency
- ✅ Prioritizing developer experience and clean code
- ✅ Need balance of performance and flexibility
- ✅ Want property wrapper-based dependency injection
다음의 경우 Needle을 선택하세요:
- ✅ Building very large enterprise applications
- ✅ Performance is absolutely critical (microsecond sensitive)
- ✅ Compile-time safety is top priority
- ✅ Can afford longer build times for runtime performance
- ✅ Team experienced with complex DI patterns
다음의 경우 Swinject를 선택하세요:
- ✅ Need maximum runtime flexibility
- ✅ Working with legacy Objective-C code
- ✅ Require complex object graphs with circular dependencies
- ✅ Building multi-tenant or highly configurable applications
- ✅ Want mature, battle-tested solution
Swift 의존성 주입의 미래는 WeaveDI와 같은 프로퍼티 래퍼 기반 솔루션으로 나아가고 있습니다. 이는 모던 Swift 기능을 수용하면서 성능과 개발자 경험의 최고 장점을 결합합니다.