[SwiftUI] EnvironmentObject 전달범위 실험

곰튀김·2022년 11월 14일
0

Dependency 를 EnvironmentObject 에 넣어서 전달하는 방식으로 DI을 사용할 경우,
최상단에서 제공하는 인스턴스가 어디까지 전달되는지 확인하는 실험

class ViewModel: ObservableObject {
    @Published var time = "Wait..."

    init() {
        Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
            self?.time = Date().description
        }
    }
}

이런 ViewModel을 만들고 View에 넣어주자

struct ContentView: View {
    @EnvironmentObject var vm: ViewModel

    var body: some View {
        VStack {
            Image(systemName: "1.square.fill")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text(vm.time)
        }
        .padding()
        .overlay(
            RoundedRectangle(cornerRadius: 16)
                .stroke(Color(UIColor.label), lineWidth: 4)
        )
    }
}

Preview 는 이렇게 되겠지

ContentView()
	.environmentObject(ViewModel())

실험1. 하위 뷰에 EnvironmentObject가 전달되는가?

struct ContentView: View {
    @EnvironmentObject var vm: ViewModel

    var body: some View {
        VStack {
            Image(systemName: "1.square.fill")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text(vm.time)

            Child()
        }
        .padding()
        .overlay(
            RoundedRectangle(cornerRadius: 16)
                .stroke(Color(UIColor.label), lineWidth: 4)
        )
    }
}

struct Child: View {
    @EnvironmentObject var vm: ViewModel

    var body: some View {
        VStack {
            Image(systemName: "2.square.fill")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text(vm.time)
        }
        .padding()
        .overlay(
            RoundedRectangle(cornerRadius: 16)
                .stroke(Color(UIColor.label), lineWidth: 4)
        )
    }
}

잘 되네!

실험2: 하위의 하위에도 잘 전달되는가?

struct Child: View {
    @EnvironmentObject var vm: ViewModel

    var body: some View {
        VStack {
            Image(systemName: "2.square.fill")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text(vm.time)

            GrandChild()
        }
        .padding()
        .overlay(
            RoundedRectangle(cornerRadius: 16)
                .stroke(Color(UIColor.label), lineWidth: 4)
        )
    }
}

struct GrandChild: View {
    @EnvironmentObject var vm: ViewModel

    var body: some View {
        VStack {
            Image(systemName: "3.square.fill")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text(vm.time)
        }
        .padding()
        .overlay(
            RoundedRectangle(cornerRadius: 16)
                .stroke(Color(UIColor.label), lineWidth: 4)
        )
    }
}

잘 되네!

실험3: 그럼, Dependecy 리만 하는 컴포넌트를 만들어서 사용해보자

struct DependencyInjected<Content>: View where Content: View {
    let content: () -> Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }

    var body: some View {
        Group {
            content()
        }
        .environmentObject(ViewModel())
    }
}

이렇게 Environment 를 제공하는 컴포넌트를 만들면, 이렇게 사용한다

DependencyInjected {
	ContentView()
}

결론: EnvironmentObject는 한 번 제공되면 View 전체에서 사용 가능하다

응용1. 최상단 뷰에서 한 번만 넣어주면 모든 페이지에서 접근 가능한 의존성을 제공할 수 있다.
응용2. 뷰와 뷰 사이에 인스턴스 전달할 필요없이 필요한 곳에서 가져다 사용할 수 있다.
응용3. Environment는 View 에서 사용되는 IoC 같은 역할을 하는구나!

검토1. EnvironmentObject는 View 에서만 사용가능하구나!
검토2. 그렇다면 ViewModel 내에서 Service 등을 DI 하기 위해서는, 별도의 Container를 구성하거나, 생성자 주입으로 넣어줘야 겠구나!

profile
사실주의 프로그래머

0개의 댓글