선언적 UI와 선언적 DI의 완벽한 만남
"SwiftUI가 UI를 혁신했다면, WeaveDI는 아키텍처를 혁신한다"
SwiftUI는 iOS 개발에 패러다임의 변화를 가져왔습니다. 선언적 UI, 상태 기반 렌더링, 컴포저블 아키텍처... 하지만 복잡한 앱을 만들다 보면 의존성 주입의 필요성을 절감하게 됩니다.
WeaveDI 3.0은 SwiftUI의 철학과 완벽하게 조화되는 선언적 의존성 주입을 제공합니다. 실제 코드를 통해 SwiftUI + WeaveDI의 무한한 가능성을 탐험해보겠습니다.
// 😰 전통적인 SwiftUI DI - EnvironmentObject의 한계
class UserService: ObservableObject {
@Published var currentUser: User?
func loadUser() {
// 복잡한 로직...
}
}
class NetworkService: ObservableObject {
func fetchData() -> AnyPublisher<Data, Error> {
// 네트워크 로직...
}
}
// 문제점 1: 루트에서 모든 서비스를 주입해야 함
struct ContentView: App {
@StateObject private var userService = UserService()
@StateObject private var networkService = NetworkService()
@StateObject private var analyticsService = AnalyticsService()
@StateObject private var configService = ConfigService()
// ... 20개 더
var body: some Scene {
WindowGroup {
MainView()
.environmentObject(userService)
.environmentObject(networkService)
.environmentObject(analyticsService)
.environmentObject(configService)
// ... 20개 더 (보일러플레이트 지옥!)
}
}
}
// 문제점 2: 깊은 뷰 계층에서의 접근 복잡성
struct DeepNestedView: View {
@EnvironmentObject var userService: UserService
@EnvironmentObject var networkService: NetworkService
@EnvironmentObject var analyticsService: AnalyticsService
// 필요한 모든 서비스를 명시적으로 선언해야 함
var body: some View {
// 만약 부모에서 environmentObject를 빠뜨리면 런타임 크래시!
}
}
// 😰 EnvironmentObject 테스트의 복잡성
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
// 모든 의존성을 수동으로 주입해야 함
.environmentObject(MockUserService())
.environmentObject(MockNetworkService())
.environmentObject(MockAnalyticsService())
// ... 계속
}
}
// 테스트에서도 마찬가지
func testUserView() {
let userService = MockUserService()
let networkService = MockNetworkService()
let view = UserView()
.environmentObject(userService)
.environmentObject(networkService)
// 테스트마다 모든 의존성을 설정해야 함
}
// ✅ WeaveDI + SwiftUI의 혁신적 통합 (실제 코드 기반)
// 서비스 정의 (ObservableObject 준수)
@MainActor
class UserService: ObservableObject {
@Published var currentUser: User?
@Published var isLoading = false
func loadUser() async {
isLoading = true
defer { isLoading = false }
// 실제 로딩 로직
try? await Task.sleep(nanoseconds: 1_000_000_000)
currentUser = User(id: "123", name: "John Doe")
}
}
@MainActor
class NetworkService: ObservableObject {
@Published var isConnected = true
func fetchData(from url: String) async throws -> Data {
// 네트워크 로직
return Data()
}
}
// WeaveDI Component로 서비스 등록
@Component
struct AppServicesComponent: ComponentProtocol, Sendable {
public init() {}
// MainActor로 격리된 서비스들을 안전하게 등록
@EnhancedProvide(scope: .singleton, factory: {
MainActor.assumeIsolated { UserService() }
})
public var userService: UserService
@EnhancedProvide(scope: .singleton, factory: {
MainActor.assumeIsolated { NetworkService() }
})
public var networkService: NetworkService
public static func registerAll(into container: DIContainer) {
let component = AppServicesComponent()
container.register(UserService.self, factory: {
MainActor.assumeIsolated { UserService() }
})
container.register(NetworkService.self, factory: {
MainActor.assumeIsolated { NetworkService() }
})
}
public static func registerAll() {
registerAll(into: DIContainer.shared)
}
}
// 🎯 SwiftUI에서 사용 - 믿을 수 없을 정도로 간단!
struct UserProfileView: View {
// ✨ 마법 같은 간단함! EnvironmentObject 없이도 완벽 동작
@StateObject private var userService: UserService = {
UnifiedDI.resolve(UserService.self) ?? UserService()
}()
@StateObject private var networkService: NetworkService = {
UnifiedDI.resolve(NetworkService.self) ?? NetworkService()
}()
var body: some View {
VStack {
if userService.isLoading {
ProgressView("사용자 정보 로딩 중...")
} else if let user = userService.currentUser {
VStack {
Text("안녕하세요, \(user.name)님!")
.font(.title)
Text("연결 상태: \(networkService.isConnected ? "연결됨" : "연결 안됨")")
.foregroundColor(networkService.isConnected ? .green : .red)
}
} else {
Button("사용자 정보 로드") {
Task {
await userService.loadUser()
}
}
}
}
.padding()
}
}
// 앱 시작 시 한 번만 초기화
struct MyApp: App {
init() {
// 🚀 한 줄로 모든 서비스 등록 완료!
AppServicesComponent.registerAll()
}
var body: some Scene {
WindowGroup {
UserProfileView()
// ✨ environmentObject 없이도 완벽 동작!
}
}
}
// ✅ SwiftUI 전용 Property Wrapper 생성
@propertyWrapper
struct DIStateObject<T: ObservableObject>: DynamicProperty where T: Sendable {
@StateObject private var object: T
init(_ type: T.Type) {
let resolved = UnifiedDI.resolve(type) ?? {
fatalError("Service \(type) not registered. Please register it using WeaveDI.")
}()
self._object = StateObject(wrappedValue: resolved)
}
var wrappedValue: T {
object
}
var projectedValue: ObservedObject<T>.Wrapper {
$object
}
}
// 🎯 사용법이 더욱 간단해짐!
struct ModernUserView: View {
@DIStateObject var userService: UserService
@DIStateObject var networkService: NetworkService
@DIStateObject var analyticsService: AnalyticsService
var body: some View {
VStack {
Text("현재 사용자: \(userService.currentUser?.name ?? "없음")")
Text("네트워크: \(networkService.isConnected ? "연결됨" : "끊김")")
Button("사용자 로드") {
Task {
await userService.loadUser()
analyticsService.track("user_loaded")
}
}
}
}
}
// ✅ Preview와 테스트가 믿을 수 없을 정도로 간단해짐
// Mock 서비스들
@MainActor
class MockUserService: UserService {
override init() {
super.init()
self.currentUser = User(id: "mock", name: "Mock User")
self.isLoading = false
}
}
@MainActor
class MockNetworkService: NetworkService {
override init() {
super.init()
self.isConnected = true
}
}
// 🎯 Preview 설정이 한 줄로 끝남!
struct UserProfileView_Previews: PreviewProvider {
static var previews: some View {
Group {
// 정상 상태 Preview
UserProfileView()
.onAppear {
// Preview용 Mock 서비스 등록
_ = UnifiedDI.register(UserService.self) { MockUserService() }
_ = UnifiedDI.register(NetworkService.self) { MockNetworkService() }
}
.previewDisplayName("정상 상태")
// 로딩 상태 Preview
UserProfileView()
.onAppear {
let mockUserService = MockUserService()
mockUserService.isLoading = true
_ = UnifiedDI.register(UserService.self) { mockUserService }
_ = UnifiedDI.register(NetworkService.self) { MockNetworkService() }
}
.previewDisplayName("로딩 중")
// 오프라인 상태 Preview
UserProfileView()
.onAppear {
let mockNetworkService = MockNetworkService()
mockNetworkService.isConnected = false
_ = UnifiedDI.register(UserService.self) { MockUserService() }
_ = UnifiedDI.register(NetworkService.self) { mockNetworkService }
}
.previewDisplayName("오프라인")
}
}
}
// 🧪 테스트도 극도로 간단해짐!
import XCTest
import SwiftUI
import ViewInspector
class UserProfileViewTests: XCTestCase {
override func setUp() {
super.setUp()
// 각 테스트마다 깨끗한 상태로 시작
UnifiedDI.releaseAll()
}
func testUserProfileDisplaysUser() throws {
// Given: Mock 서비스 등록
let mockUser = User(id: "test", name: "Test User")
let mockUserService = MockUserService()
mockUserService.currentUser = mockUser
_ = UnifiedDI.register(UserService.self) { mockUserService }
_ = UnifiedDI.register(NetworkService.self) { MockNetworkService() }
// When: View 생성
let view = UserProfileView()
// Then: 사용자 이름이 표시됨
let text = try view.inspect().find(text: "안녕하세요, Test User님!")
XCTAssertNotNil(text)
}
func testLoadingState() throws {
// Given: 로딩 중인 서비스
let mockUserService = MockUserService()
mockUserService.isLoading = true
_ = UnifiedDI.register(UserService.self) { mockUserService }
_ = UnifiedDI.register(NetworkService.self) { MockNetworkService() }
// When: View 생성
let view = UserProfileView()
// Then: 로딩 인디케이터가 표시됨
let progressView = try view.inspect().find(ViewType.ProgressView.self)
XCTAssertNotNil(progressView)
}
}
// ✅ 깊은 뷰 계층에서도 완벽하게 동작
struct MainTabView: View {
var body: some View {
TabView {
NavigationView {
HomeView()
}
.tabItem {
Image(systemName: "house")
Text("홈")
}
NavigationView {
ProfileNavigationView()
}
.tabItem {
Image(systemName: "person")
Text("프로필")
}
NavigationView {
SettingsView()
}
.tabItem {
Image(systemName: "gear")
Text("설정")
}
}
}
}
struct ProfileNavigationView: View {
var body: some View {
List {
NavigationLink("사용자 정보", destination: UserDetailView())
NavigationLink("계정 설정", destination: AccountSettingsView())
NavigationLink("알림 설정", destination: NotificationSettingsView())
}
.navigationTitle("프로필")
}
}
struct UserDetailView: View {
// 🎯 아무리 깊은 뷰에서도 간단하게 서비스 접근!
@DIStateObject var userService: UserService
@DIStateObject var analyticsService: AnalyticsService
var body: some View {
VStack(alignment: .leading, spacing: 16) {
if let user = userService.currentUser {
VStack(alignment: .leading) {
Text("사용자 ID: \(user.id)")
Text("이름: \(user.name)")
Text("이메일: \(user.email)")
}
Button("프로필 수정") {
analyticsService.track("profile_edit_tapped")
// 프로필 수정 로직
}
Button("로그아웃") {
Task {
await userService.logout()
analyticsService.track("user_logged_out")
}
}
} else {
Text("사용자 정보를 불러올 수 없습니다.")
Button("다시 시도") {
Task {
await userService.loadUser()
}
}
}
}
.padding()
.navigationTitle("사용자 정보")
.task {
// 뷰가 나타날 때 자동으로 사용자 정보 로드
if userService.currentUser == nil {
await userService.loadUser()
}
}
}
}
// ✅ WeaveDI로 강화된 MVVM 패턴
// ViewModel도 WeaveDI로 관리
@MainActor
class UserListViewModel: ObservableObject {
@Published var users: [User] = []
@Published var isLoading = false
@Published var errorMessage: String?
private let userService: UserService
private let analyticsService: AnalyticsService
init(
userService: UserService,
analyticsService: AnalyticsService
) {
self.userService = userService
self.analyticsService = analyticsService
}
func loadUsers() async {
isLoading = true
errorMessage = nil
defer { isLoading = false }
do {
users = try await userService.fetchAllUsers()
analyticsService.track("users_loaded", properties: ["count": users.count])
} catch {
errorMessage = error.localizedDescription
analyticsService.track("users_load_failed", properties: ["error": error.localizedDescription])
}
}
func refresh() async {
await loadUsers()
}
}
// ViewModel도 Component로 등록
@Component
struct ViewModelComponent: ComponentProtocol, Sendable {
public init() {}
@EnhancedProvide(scope: .transient, factory: {
let userService = UnifiedDI.requireResolve(UserService.self)
let analyticsService = UnifiedDI.requireResolve(AnalyticsService.self)
return MainActor.assumeIsolated {
UserListViewModel(
userService: userService,
analyticsService: analyticsService
)
}
})
public var userListViewModel: UserListViewModel
public static func registerAll(into container: DIContainer) {
let component = ViewModelComponent()
container.register(UserListViewModel.self, factory: {
let userService = UnifiedDI.requireResolve(UserService.self)
let analyticsService = UnifiedDI.requireResolve(AnalyticsService.self)
return MainActor.assumeIsolated {
UserListViewModel(
userService: userService,
analyticsService: analyticsService
)
}
})
}
public static func registerAll() {
registerAll(into: DIContainer.shared)
}
}
// SwiftUI View에서 ViewModel 사용
struct UserListView: View {
@DIStateObject var viewModel: UserListViewModel
var body: some View {
NavigationView {
Group {
if viewModel.isLoading {
ProgressView("사용자 목록 로딩 중...")
} else if let errorMessage = viewModel.errorMessage {
VStack {
Text("오류 발생")
.font(.headline)
Text(errorMessage)
.foregroundColor(.red)
Button("다시 시도") {
Task {
await viewModel.refresh()
}
}
}
} else {
List(viewModel.users) { user in
NavigationLink(destination: UserDetailView()) {
VStack(alignment: .leading) {
Text(user.name)
.font(.headline)
Text(user.email)
.font(.caption)
.foregroundColor(.secondary)
}
}
}
}
}
.navigationTitle("사용자 목록")
.refreshable {
await viewModel.refresh()
}
.task {
if viewModel.users.isEmpty {
await viewModel.loadUsers()
}
}
}
}
}
// ✅ TCA와 WeaveDI의 완벽한 통합 (실제 DependenciesBridge.swift 활용)
import ComposableArchitecture
// TCA Reducer에서 WeaveDI 서비스 사용
struct UserFeature: Reducer {
struct State: Equatable {
var user: User?
var isLoading = false
var errorMessage: String?
}
enum Action: Equatable {
case loadUser
case userLoaded(User)
case loadingFailed(String)
}
// WeaveDI와 TCA의 자동 브릿지 활용
@Dependency(\.userService) var userService
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .loadUser:
state.isLoading = true
state.errorMessage = nil
return .run { send in
do {
// WeaveDI 서비스 직접 사용!
let user = try await userService.loadCurrentUser()
await send(.userLoaded(user))
} catch {
await send(.loadingFailed(error.localizedDescription))
}
}
case .userLoaded(let user):
state.isLoading = false
state.user = user
return .none
case .loadingFailed(let error):
state.isLoading = false
state.errorMessage = error
return .none
}
}
}
}
// TCA DependencyKey를 WeaveDI와 자동 연동 (실제 코드)
extension DependencyValues {
var userService: UserService {
get {
// WeaveDI에서 우선 조회, 없으면 TCA 기본값
UnifiedDI.resolve(UserService.self) ?? self[UserServiceKey.self]
}
set {
// 양방향 동기화: TCA와 WeaveDI 모두에 등록
self[UserServiceKey.self] = newValue
_ = UnifiedDI.register(UserService.self) { newValue }
}
}
}
private enum UserServiceKey: DependencyKey {
static let liveValue: UserService = UserService()
static let testValue: UserService = MockUserService()
static let previewValue: UserService = MockUserService()
}
// SwiftUI에서 TCA Store 사용
struct UserFeatureView: View {
let store: StoreOf<UserFeature>
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
VStack {
if viewStore.isLoading {
ProgressView("사용자 정보 로딩 중...")
} else if let user = viewStore.user {
VStack {
Text("사용자: \(user.name)")
Text("이메일: \(user.email)")
}
} else if let error = viewStore.errorMessage {
VStack {
Text("오류: \(error)")
Button("다시 시도") {
viewStore.send(.loadUser)
}
}
} else {
Button("사용자 로드") {
viewStore.send(.loadUser)
}
}
}
.task {
viewStore.send(.loadUser)
}
}
}
}
// 앱에서 TCA + WeaveDI 함께 사용
struct TCAApp: App {
init() {
// WeaveDI 서비스 등록
AppServicesComponent.registerAll()
ViewModelComponent.registerAll()
}
var body: some Scene {
WindowGroup {
UserFeatureView(
store: Store(initialState: UserFeature.State()) {
UserFeature()
}
)
}
}
}
// ✅ SwiftUI 생명주기에 맞춘 스코프 관리
struct SessionScopedView: View {
let sessionId: String
var body: some View {
VStack {
SessionContentView()
}
.onAppear {
// 세션 스코프 서비스 등록
setupSessionScope()
}
.onDisappear {
// 세션 종료 시 스코프 정리
cleanupSessionScope()
}
}
private func setupSessionScope() {
Task {
// UnifiedRegistry의 스코프 시스템 활용
let registry = UnifiedRegistry.shared
await registry.registerScoped(
SessionService.self,
scope: .custom("session_\(sessionId)"),
factory: { SessionService(sessionId: sessionId) }
)
await registry.registerScoped(
SessionAnalyticsService.self,
scope: .custom("session_\(sessionId)"),
factory: { SessionAnalyticsService(sessionId: sessionId) }
)
}
}
private func cleanupSessionScope() {
Task {
let registry = UnifiedRegistry.shared
let cleanedCount = await registry.releaseScope(
kind: .custom("session_\(sessionId)"),
id: sessionId
)
print("세션 스코프 정리 완료: \(cleanedCount)개 서비스 해제")
}
}
}
struct SessionContentView: View {
@DIStateObject var sessionService: SessionService
@DIStateObject var sessionAnalytics: SessionAnalyticsService
var body: some View {
VStack {
Text("세션 ID: \(sessionService.sessionId)")
Text("활동 시간: \(sessionAnalytics.sessionDuration)")
Button("이벤트 기록") {
sessionAnalytics.trackEvent("button_tapped")
}
}
}
}
// ✅ 성능 최적화를 위한 고급 패턴
// 지연 로딩 서비스 래퍼
@propertyWrapper
struct LazyDIStateObject<T: ObservableObject>: DynamicProperty where T: Sendable {
@State private var object: T?
private let serviceType: T.Type
init(_ type: T.Type) {
self.serviceType = type
}
var wrappedValue: T {
if let object = object {
return object
} else {
let resolved = UnifiedDI.resolve(serviceType) ?? {
fatalError("Service \(serviceType) not registered")
}()
object = resolved
return resolved
}
}
var projectedValue: Binding<T> {
Binding(
get: { wrappedValue },
set: { object = $0 }
)
}
}
// 조건부 서비스 로딩
struct ConditionalServiceView: View {
@State private var needsAdvancedFeatures = false
@DIStateObject var basicService: BasicService
@LazyDIStateObject var advancedService: AdvancedService // 필요할 때만 로딩
var body: some View {
VStack {
Text("기본 기능")
Button("기본 작업 실행") {
basicService.performBasicTask()
}
Toggle("고급 기능 활성화", isOn: $needsAdvancedFeatures)
if needsAdvancedFeatures {
VStack {
Text("고급 기능")
Button("고급 작업 실행") {
// 이 시점에서 advancedService가 처음 로딩됨
advancedService.performAdvancedTask()
}
}
}
}
}
}
// 메모리 효율적인 대용량 리스트
struct OptimizedListView: View {
@DIStateObject var dataService: DataService
var body: some View {
List {
ForEach(dataService.items.indices, id: \.self) { index in
OptimizedRowView(index: index)
}
}
.task {
// 백그라운드에서 데이터 로딩
await dataService.loadData()
}
}
}
struct OptimizedRowView: View {
let index: Int
// 행별로 필요한 서비스만 지연 로딩
@LazyDIStateObject var imageService: ImageService
var body: some View {
HStack {
AsyncImage(url: imageService.getImageURL(for: index)) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
} placeholder: {
ProgressView()
}
.frame(width: 50, height: 50)
Text("항목 \(index)")
}
}
}
// ✅ 실시간 성능 모니터링 SwiftUI 뷰
struct DIPerformanceDashboard: View {
@State private var performanceMetrics: DIPerformanceMetrics?
@State private var timer: Timer?
var body: some View {
NavigationView {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
if let metrics = performanceMetrics {
PerformanceCard(
title: "등록된 서비스",
value: "\(metrics.registeredServices)개",
color: .blue
)
PerformanceCard(
title: "해결된 서비스",
value: "\(metrics.resolvedServices)개",
color: .green
)
PerformanceCard(
title: "평균 해결 시간",
value: String(format: "%.2fμs", metrics.avgResolutionTime),
color: .orange
)
PerformanceCard(
title: "캐시 히트율",
value: String(format: "%.1f%%", metrics.cacheHitRate * 100),
color: .purple
)
VStack(alignment: .leading) {
Text("자주 사용되는 서비스")
.font(.headline)
ForEach(metrics.topUsedServices, id: \.self) { service in
Text("• \(service)")
.font(.caption)
}
}
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
if !metrics.optimizationSuggestions.isEmpty {
VStack(alignment: .leading) {
Text("최적화 제안")
.font(.headline)
ForEach(metrics.optimizationSuggestions, id: \.self) { suggestion in
Text("💡 \(suggestion)")
.font(.caption)
.foregroundColor(.blue)
}
}
.padding()
.background(Color.blue.opacity(0.1))
.cornerRadius(8)
}
} else {
ProgressView("성능 데이터 로딩 중...")
}
}
.padding()
}
.navigationTitle("DI 성능 모니터")
.onAppear {
startMonitoring()
}
.onDisappear {
stopMonitoring()
}
}
}
private func startMonitoring() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
updateMetrics()
}
updateMetrics()
}
private func stopMonitoring() {
timer?.invalidate()
timer = nil
}
private func updateMetrics() {
Task {
let optimizer = AutoDIOptimizer.shared
let registry = UnifiedRegistry.shared
let metrics = DIPerformanceMetrics(
registeredServices: await registry.registeredTypeCount(),
resolvedServices: optimizer.getResolvedTypes().count,
avgResolutionTime: measureAverageResolutionTime(),
cacheHitRate: 0.92, // FastResolveCache 히트율
topUsedServices: optimizer.getTopUsedTypes(limit: 5),
optimizationSuggestions: optimizer.getOptimizationSuggestions()
)
await MainActor.run {
performanceMetrics = metrics
}
}
}
private func measureAverageResolutionTime() -> Double {
let iterations = 100
let startTime = Date()
for _ in 0..<iterations {
_ = UnifiedDI.resolve(UserService.self)
}
let totalTime = Date().timeIntervalSince(startTime)
return (totalTime / Double(iterations)) * 1_000_000 // microseconds
}
}
struct PerformanceCard: View {
let title: String
let value: String
let color: Color
var body: some View {
VStack(alignment: .leading) {
Text(title)
.font(.caption)
.foregroundColor(.secondary)
Text(value)
.font(.title2)
.fontWeight(.bold)
.foregroundColor(color)
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(color.opacity(0.1))
.cornerRadius(8)
}
}
struct DIPerformanceMetrics {
let registeredServices: Int
let resolvedServices: Int
let avgResolutionTime: Double
let cacheHitRate: Double
let topUsedServices: [String]
let optimizationSuggestions: [String]
}
// 개발자 도구 뷰에 통합
struct DeveloperToolsView: View {
var body: some View {
List {
NavigationLink("DI 성능 모니터", destination: DIPerformanceDashboard())
NavigationLink("의존성 그래프", destination: DependencyGraphView())
NavigationLink("서비스 목록", destination: ServiceListView())
}
.navigationTitle("개발자 도구")
}
}
// ✅ SwiftUI + WeaveDI 베스트 프랙티스
struct BestPracticesGuide {
static let rules = [
"🎯 서비스 생명주기": [
"ObservableObject를 구현하는 서비스는 @MainActor로 격리",
"UI 업데이트가 필요한 서비스만 ObservableObject 채택",
"비즈니스 로직 서비스는 일반 class로 구현",
"싱글톤이 적절한 서비스는 .singleton 스코프 사용"
],
"🔄 상태 관리": [
"@DIStateObject는 뷰의 소유 서비스에만 사용",
"공유 상태는 EnvironmentObject와 병행 사용 고려",
"일시적 상태는 @State, 지속적 상태는 DI 서비스 활용",
"복잡한 상태 로직은 ViewModel 패턴 적용"
],
"⚡ 성능 최적화": [
"자주 사용되는 서비스는 앱 시작 시 prewarm",
"무거운 서비스는 @LazyDIStateObject로 지연 로딩",
"뷰별 스코프가 필요하면 커스텀 스코프 활용",
"불필요한 서비스 의존성 제거"
],
"🧪 테스트 전략": [
"Preview에서는 Mock 서비스로 등록 교체",
"테스트마다 UnifiedDI.releaseAll()로 초기화",
"통합 테스트에서는 실제 서비스 조합 사용",
"UI 테스트는 ViewInspector 활용"
]
]
static func printGuidelines() {
print("📚 SwiftUI + WeaveDI 베스트 프랙티스 가이드")
print("=" * 60)
for (category, practices) in rules {
print("\n\(category):")
practices.forEach { print(" • \(practice)") }
}
}
}
// 가이드 출력
BestPracticesGuide.printGuidelines()
struct SwiftUIAdoptionRoadmap {
static let phases = [
"1주차: 기본 통합": [
"핵심 서비스 WeaveDI로 등록",
"@DIStateObject 도입",
"기본 MVVM 패턴 적용"
],
"2주차: 고급 패턴": [
"TCA 통합 (필요 시)",
"스코프 기반 서비스 관리",
"성능 최적화 적용"
],
"3주차: 테스트 강화": [
"Preview Mock 시스템",
"단위 테스트 작성",
"UI 테스트 자동화"
],
"4주차: 모니터링": [
"성능 대시보드 구축",
"실시간 모니터링 시스템",
"최적화 제안 시스템"
]
]
static func showRoadmap() {
print("🗺️ SwiftUI + WeaveDI 도입 로드맵")
phases.forEach { phase, tasks in
print("\n\(phase):")
tasks.forEach { print(" ✓ \($0)") }
}
}
}
SwiftUIAdoptionRoadmap.showRoadmap()
SwiftUI와 WeaveDI의 완벽한 조화를 마스터했다면, 이제 크로스 플랫폼 전략으로 나아갈 차례입니다. 다음 글에서는 iOS, macOS, watchOS 전반에 걸친 WeaveDI 활용법을 알아보겠습니다.
다음 글 미리보기: "크로스 플랫폼 전략: WeaveDI로 iOS/macOS/watchOS 통합하기"
SwiftUI + WeaveDI의 조합은 단순한 기술 통합을 넘어서 iOS 개발의 새로운 패러다임을 제시합니다. 선언적 UI와 선언적 DI가 만나 더 빠르고, 더 안전하고, 더 아름다운 앱을 만들어보세요! ✨
No newline at end of file