빠른 시작 가이드
5분 안에 WeaveDI를 시작해보세요. 이 포괄적인 가이드는 Swift 5와 Swift 6 호환성, 상세한 코드 설명, 실제 통합 예제를 다룹니다.
설치
Swift 버전 요구사항
WeaveDI는 다양한 Swift 버전을 지원하며 각 버전에 최적화된 기능을 제공합니다:
Swift 버전 | iOS 버전 | macOS 버전 | 기능 |
---|---|---|---|
Swift 6.0+ | iOS 17.0+ | macOS 14.0+ | 🔥 완전한 동시성 기능, 엄격한 Sendable 준수, actor 격리 |
Swift 5.9+ | iOS 16.0+ | macOS 13.0+ | ✅ 완전한 async/await 지원, 프로퍼티 래퍼, 성능 최적화 |
Swift 5.8+ | iOS 15.0+ | macOS 12.0+ | ✅ 핵심 의존성 주입, 기본 동시성 지원 |
Swift 5.7+ | iOS 14.0+ | macOS 11.0+ | ⚠️ 제한적 동시성, 폴백 구현 |
Swift Package Manager
프로젝트의 Package.swift 파일에 WeaveDI를 추가하세요. 이 설정은 Swift Package Manager가 GitHub 리포지토리에서 WeaveDI 버전 3.2.0 이상을 다운로드하도록 지시합니다:
// Package.swift
dependencies: [
.package(url: "https://github.com/Roy-wonji/WeaveDI.git", from: "3.2.0")
],
targets: [
.target(
name: "YourApp",
dependencies: ["WeaveDI"],
// Swift 6 엄격한 동시성을 위한 컴파일러 플래그
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency") // Swift 6 전용
]
)
]
작동 원리:
- 공식 리포지토리에서 WeaveDI 프레임워크를 다운로드합니다
- 최신 기능과 버그 수정이 포함된 3.2.0 이상 버전을 보장합니다
- Swift 프로젝트의 빌드 시스템과 원활하게 통합됩니다
- 최대 안전성을 위한 Swift 6 엄격한 동시성 검사를 활성화합니다
Xcode 통합
Swift 6 프로젝트용
- File → Add Package Dependencies
- 입력:
https://github.com/Roy-wonji/WeaveDI.git
- "3.2.0"부터 "Up to Next Major Version" 선택
- Add to Target
- Swift 6 설정 구성:
- Target → Build Settings → Swift Language Version → Swift 6
- Target → Build Settings → Other Swift Flags →
-strict-concurrency=complete
추가
Swift 5 프로젝트용
- File → Add Package Dependencies
- 입력:
https://github.com/Roy-wonji/WeaveDI.git
- Swift 버전에 맞는 버전 범위 선택:
- Swift 5.9+: 최신 버전 사용 (3.2.0+)
- Swift 5.8: 3.0.x 브랜치 사용
- Swift 5.7: 호환성을 위해 2.x.x 사용
검증:
// WeaveDI가 올바르게 통합되었는지 확인하려면 이것을 추가하세요
import WeaveDI
print("WeaveDI 버전: \(WeaveDI.version)") // 현재 버전을 출력해야 합니다
print("Swift 버전: \(#if swift(>=6.0) "6.0+" #elseif swift(>=5.9) "5.9+" #else "5.8 이하" #endif)")
기본 사용법
1. Import
의존성 주입이 필요한 Swift 파일에 WeaveDI를 import하세요. 이를 통해 프로퍼티 래퍼, 등록 API, 컨테이너 관리 등 모든 WeaveDI 기능에 접근할 수 있습니다:
import WeaveDI
사용 가능한 기능:
@Injected
(v3.2.0+),@Factory
,@Inject
(deprecated),@SafeInject
(deprecated) 프로퍼티 래퍼 접근- UnifiedDI 등록 및 해결 API
- WeaveDI.Container 부트스트랩 기능
- 모든 WeaveDI 유틸리티 클래스와 프로토콜
2. 서비스 정의
서비스를 위한 프로토콜(인터페이스)과 구현을 생성하세요. 이는 의존성 역전 원칙을 따릅니다 - 구체적인 구현이 아닌 추상화에 의존하세요:
// 서비스 계약 정의 (사용 가능한 기능)
protocol UserService {
func fetchUser(id: String) async -> User?
}
// 실제 서비스 로직 구현
class UserServiceImpl: UserService {
func fetchUser(id: String) async -> User? {
// 실제 앱에서는 API나 데이터베이스를 호출할 것입니다
// 데모 목적으로 간단한 User 객체를 반환합니다
return User(id: id, name: "John")
}
}
프로토콜을 사용하는 이유:
- 테스트 가능성: 테스트용 모킹 구현을 쉽게 생성할 수 있습니다
- 유연성: 의존 코드를 변경하지 않고 구현을 교체할 수 있습니다
- 유지보수성: 인터페이스와 구현의 명확한 분리
- 모범 사례: 깔끔한 아키텍처를 위한 SOLID 원칙을 따릅니다
3. 의존성 등록
권장: @Injected with InjectedKey (v3.2.0+)
// 1. InjectedKey 정의
struct UserServiceKey: InjectedKey {
static var liveValue: UserService = UserServiceImpl()
static var testValue: UserService = MockUserService()
}
// 2. InjectedValues 확장
extension InjectedValues {
var userService: UserService {
get { self[UserServiceKey.self] }
set { self[UserServiceKey.self] = newValue }
}
}
@Injected 등록 작동 방식:
- InjectedKey 프로토콜: 의존성의 live 및 test 값을 정의합니다
- InjectedValues 확장: KeyPath 기반 접근자를 제공합니다
- 타입 안전성: KeyPath 해결로 컴파일 타임 안전성을 보장합니다
- 테스트 지원:
withInjectedValues
로 내장된 테스트 값 지원
레거시: UnifiedDI 등록 (v3.2.0부터 Deprecated)
// 앱 시작 시 등록 - 프로토콜과 구현 간의 바인딩을 생성합니다
let userService = UnifiedDI.register(UserService.self) {
UserServiceImpl() // 실제 구현을 생성하는 팩토리 클로저
}
등록 작동 방식:
- 타입 등록:
UserService
프로토콜을UserServiceImpl
클래스에 매핑합니다 - 팩토리 클로저:
{ UserServiceImpl() }
클로저가 인스턴스 생성 방법을 정의합니다 - 지연 생성: 인스턴스는 처음 요청될 때만 생성됩니다 (지연 로딩)
- 기본 싱글톤: 다르게 구성하지 않는 한 동일한 인스턴스가 앱 전체에서 재사용됩니다
- 반환 값: 필요한 경우 즉시 사용할 수 있도록 생성된 인스턴스를 반환합니다
4. Property Wrapper 사용
권장: @Injected (v3.2.0+)
class UserViewController {
// @Injected는 KeyPath를 통해 해결 - 타입 안전하고 TCA 스타일
@Injected(\.userService) var userService
func loadUser() async {
// 주입된 서비스를 직접 사용 (non-optional)
let user = await userService.fetchUser(id: "123")
// 가져온 데이터로 UI 업데이트
DispatchQueue.main.async {
// 여기서 UI를 업데이트하세요
print("✅ 사용자 로드됨: \(user?.name ?? "알 수 없음")")
}
}
}
@Injected 작동 방식:
- KeyPath 해결:
InjectedValues
와 함께 컴파일 타임 안전한 KeyPath 사용 - Non-Optional: 값을 직접 반환 (liveValue 또는 testValue가 폴백)
- 타입 안전: 컴파일 타임 타입 검사
- TCA 호환: TCA 개발자에게 친숙한 패턴
레거시: @Inject (v3.2.0부터 Deprecated)
class UserViewController {
// @Inject는 DI 컨테이너에서 UserService를 자동으로 해결합니다
// '?'는 옵셔널로 만듭니다 - 서비스가 등록되지 않았어도 앱이 크래시되지 않습니다
@Inject var userService: UserService? // ⚠️ Deprecated
func loadUser() async {
// 주입된 의존성을 항상 안전하게 언래핑하세요
guard let service = userService else {
print("❌ UserService를 사용할 수 없습니다")
return
}
// 주입된 서비스를 사용하여 작업 수행
let user = await service.fetchUser(id: "123")
// 가져온 데이터로 UI 업데이트
DispatchQueue.main.async {
// 여기서 UI를 업데이트하세요
print("✅ 사용자 로드됨: \(user?.name ?? "알 수 없음")")
}
}
}
@Inject 작동 방식:
- 자동 해결: WeaveDI가 등록된 구현을 자동으로 찾아 주입합니다
- 옵셔널 안전성: 서비스가 등록되지 않았으면
nil
을 반환합니다 (크래시 방지) - 지연 로딩: 서비스는 처음 접근될 때만 해결됩니다
- 스레드 안전: 다양한 스레드와 액터에서 안전하게 사용할 수 있습니다
Property Wrapper
@Injected - 현대적 의존성 주입 (v3.2.0+)
타입 안전하고 TCA 스타일의 KeyPath 접근을 통한 현대적인 의존성 주입에 @Injected
를 사용하세요:
// InjectedKey 정의
struct APIClientKey: InjectedKey {
static var liveValue: APIClient = URLSessionAPIClient()
static var testValue: APIClient = MockAPIClient()
}
// InjectedValues 확장
extension InjectedValues {
var apiClient: APIClient {
get { self[APIClientKey.self] }
set { self[APIClientKey.self] = newValue }
}
}
// @Injected 사용
class ViewModel {
@Injected(\.apiClient) var apiClient
@Injected(\.userService) var userService
func loadData() async {
let data = await apiClient.fetchData()
let user = await userService.fetchUser(id: "123")
}
}
@Injected를 사용하는 경우:
- 모든 새 코드: 새로운 개발에 권장 (v3.2.0+)
- 타입 안전성: 컴파일 타임 타입 검사를 원할 때
- TCA 프로젝트: TCA 개발자에게 친숙한 패턴
- 테스팅:
withInjectedValues
로 쉽게 오버라이드
@Inject - 선택적 의존성 (v3.2.0부터 Deprecated)
대부분의 의존성 주입 시나리오에서 @Inject
를 사용하세요. 의존성이 등록되지 않았어도 앱을 크래시시키지 않는 안전한 옵셔널 주입을 제공합니다:
class ViewController {
// 표준 의존성 주입 - 안전하고 옵셔널
@Inject var userService: UserService?
func viewDidLoad() {
super.viewDidLoad()
// 안전한 옵셔널 체이닝 - 서비스가 nil이어도 크래시되지 않습니다
userService?.fetchUser(id: "current") { [weak self] user in
DispatchQueue.main.async {
self?.displayUser(user)
}
}
// 대안: 더 나은 오류 처리를 위한 명시적 nil 확인
guard let service = userService else {
showErrorMessage("사용자 서비스를 사용할 수 없습니다")
return
}
// 이제 서비스가 사용 가능함을 알 수 있습니다
service.fetchUser(id: "current") { user in
// 사용자 데이터 처리
}
}
}
@Inject를 사용하는 경우:
- 레거시 코드: 기존 코드 유지보수
- 선택적 의존성: 중요하지 않지만 있으면 좋은 서비스
- 안전한 주입: 누락된 의존성으로 인한 크래시를 방지하고 싶을 때
- 테스팅: 실제 서비스를 등록하지 않아 쉽게 모킹 가능
@Factory - 매번 새 인스턴스
공유 싱글톤이 아닌 새로운 인스턴스가 필요할 때 @Factory
를 사용하세요. 상태가 없는 작업이나 격리된 인스턴스가 필요할 때 완벽합니다:
class DocumentProcessor {
// @Factory는 접근할 때마다 새로운 PDFGenerator 인스턴스를 생성합니다
// 각 문서가 자체 생성기를 가져 상태 충돌을 방지합니다
@Factory var pdfGenerator: PDFGenerator
func createDocument(content: String) {
// pdfGenerator에 접근할 때마다 완전히 새로운 인스턴스를 반환합니다
let generator = pdfGenerator // 여기서 새 인스턴스가 생성됩니다
// 이 특정 생성기를 구성합니다
generator.setContent(content)
generator.setFormat(.A4)
// PDF 생성
let pdfData = generator.generate()
savePDF(pdfData)
}
func createMultipleDocuments(contents: [String]) {
for content in contents {
// 각 반복마다 완전히 새로운 PDFGenerator를 얻습니다
let generator = pdfGenerator // 각 문서마다 새로운 인스턴스
generator.setContent(content)
let pdf = generator.generate()
savePDF(pdf)
// 재설정이나 정리가 필요 없습니다 - 각 생성기는 독립적입니다
}
}
}
@Factory를 사용하는 경우:
- 상태가 없는 작업: PDF 생성, 이미지 처리, 데이터 변환
- 동시 처리: 각 스레드/작업이 자체 인스턴스가 필요한 경우
- 공유 상태 방지: 한 작업이 다른 작업에 영향을 주지 않게 하기
- 빌더 패턴: 각 구성마다 새로운 빌더
- 수명이 짧은 객체: 지속될 필요가 없는 객체
@SafeInject - 에러 처리 (v3.2.0부터 Deprecated)
누락된 의존성에 대한 명시적 오류 처리가 필요할 때 @SafeInject
를 사용하세요. 이 래퍼는 의존성 해결 실패에 대한 더 많은 제어를 제공합니다:
class DataManager {
// @SafeInject는 해결이 실패할 때 명시적인 오류 정보를 제공합니다
@SafeInject var database: Database?
func save(_ data: Data) throws {
// 의존성 주입이 성공했는지 확인
guard let db = database else {
// 디버깅을 위한 특정 오류 로그
print("❌ Database 의존성을 찾을 수 없습니다 - DI 등록을 확인하세요")
// 호출자를 위한 설명적인 오류 던지기
throw DIError.dependencyNotFound(type: "Database")
}
// 데이터베이스 작업 진행
try db.save(data)
print("✅ 데이터가 성공적으로 저장되었습니다")
}
func safeSave(_ data: Data) -> Result<Void, Error> {
do {
guard let db = database else {
return .failure(DIError.dependencyNotFound(type: "Database"))
}
try db.save(data)
return .success(())
} catch {
return .failure(error)
}
}
}
// 더 나은 오류 처리를 위한 커스텀 오류 타입
enum DIError: LocalizedError {
case dependencyNotFound(type: String)
var errorDescription: String? {
switch self {
case .dependencyNotFound(let type):
return "필수 의존성 '\(type)'을 찾을 수 없습니다. DI 컨테이너에 등록해 주세요."
}
}
}
@SafeInject를 사용하는 경우:
- 레거시 코드: 기존 코드 유지보수
- 오류 보고: 누락된 의존성에 대한 상세한 오류 정보가 필요할 때
- 명시적 실패 처리:
nil
이 충분히 설명적이지 않을 때 - 프로덕션 디버깅: 로그에서 더 나은 진단 정보를 얻기 위해
참고: v3.2.0부터는
@Injected
를 사용하는 것을 권장합니다.withInjectedValues
를 통한 더 나은 테스트 지원과 타입 안전성을 제공합니다.
고급 기능
런타임 최적화
WeaveDI는 프로덕션 앱에서 의존성 해결 속도를 크게 향상시킬 수 있는 내장 성능 최적화를 포함합니다:
// 자동 런타임 최적화 활성화
// 이는 앱 라이프사이클 초기에, 일반적으로 AppDelegate나 App.swift에서 호출해야 합니다
UnifiedRegistry.shared.enableOptimization()
// 최적화 시스템은 다음을 수행합니다:
// 1. 빠른 접근을 위해 자주 해결되는 의존성을 캐시합니다
// 2. 최소한의 해결 오버헤드를 위해 의존성 그래프를 최적화합니다
// 3. 더 나은 메모리 관리를 위한 지연 로딩 전략을 사용합니다
// 4. 성능을 모니터링하고 사용 패턴에 따라 자동 튜닝합니다
print("🚀 WeaveDI 최적화 활성화됨 - 더 나은 성능을 기대하세요!")
최적화가 하는 일:
- Hot Path 캐싱: 자주 접근되는 의존성이 즉시 해결을 위해 캐시됩니다
- 그래프 최적화: 의존성 해결 경로가 최소한의 오버헤드를 위해 최적화됩니다
- 메모리 관리: 메모리 압박 하에서 사용되지 않는 의존성의 자동 정리
- 성능 모니터링: 지속적인 개선을 위한 해결 패턴의 실시간 분석
활성화하는 경우:
- 프로덕션 빌드: 최고의 성능을 위해 릴리스 빌드에서 항상 활성화
- 대형 애플리케이션: 많은 의존성을 가진 앱에 필수
- 성능 중요 앱: 게임, 실시간 앱, 또는 엄격한 성능 요구사항이 있는 앱
Bootstrap 패턴
Bootstrap 패턴은 한 곳에서 모든 의존성을 설정하는 권장 방법입니다. 이는 적절한 초기화 순서를 보장하고 의존성 관리를 더 체계적으로 만듭니다:
// 앱 시작 시 모든 의존성 부트스트랩
// 이는 일반적으로 App.swift나 AppDelegate에서 호출됩니다
await WeaveDI.Container.bootstrap { container in
// 논리적 순서로 서비스 등록
// 1. 핵심 인프라 서비스 먼저
container.register(LoggerProtocol.self) {
ConsoleLogger() // 디버깅을 위한 기본 로깅
}
// 2. 데이터 레이어 서비스
container.register(DatabaseService.self) {
CoreDataService() // 데이터베이스 레이어
}
// 3. 네트워크 서비스
container.register(NetworkService.self) {
URLSessionNetworkService() // HTTP 클라이언트
}
// 4. 비즈니스 로직 서비스 (인프라에 의존)
container.register(UserService.self) {
UserServiceImpl() // 데이터베이스와 네트워크 서비스를 자동으로 사용
}
// 5. 프레젠테이션 레이어 서비스
container.register(AnalyticsService.self) {
FirebaseAnalytics() // 사용자 추적 및 분석
}
print("✅ 모든 의존성이 성공적으로 등록되었습니다")
}
// 대안: 환경별 부트스트랩
#if DEBUG
await WeaveDI.Container.bootstrap { container in
// 개발용 모킹 서비스 사용
container.register(UserService.self) { MockUserService() }
container.register(NetworkService.self) { MockNetworkService() }
}
#else
await WeaveDI.Container.bootstrap { container in
// 프로덕션용 실제 서비스 사용
container.register(UserService.self) { UserServiceImpl() }
container.register(NetworkService.self) { URLSessionNetworkService() }
}
#endif
Bootstrap 패턴의 장점:
- 중앙화된 설정: 모든 의존성 등록이 한 곳에
- 적절한 순서: 의존성이 논리적 순서로 등록됩니다
- 환경 인식: 디버그/릴리스 빌드에 대한 다른 설정
- 오류 감지: 누락되거나 잘못 구성된 의존성을 쉽게 발견
- 문서화: 앱의 의존성에 대한 명확한 맵 역할
다음 단계
- Property Wrapper - 상세한 주입 패턴
- Core API - 완전한 API 레퍼런스
- 런타임 최적화 - 성능 튜닝