@EnvironmentObject: 개념, 의존성 주입 및 데이터 접근

저자: IT Sectr 게시일: 2026-06-26 읽는 시간: 9 분

@EnvironmentObject는 SwiftUI의 property wrapper로, 계층 구조의 모든 View가 초기화자 체인을 통해 명시적으로 전달하지 않고 ObservableObject에 접근할 수 있게 합니다. 객체는 .environmentObject() 수정자를 사용하여 계층 구조의 특정 수준에서 환경에 주입되며, 이후 모든 하위 View가 @EnvironmentObject를 통해 접근할 수 있습니다. 이는 객체를 사용하지 않는 중간 View를 통해 객체를 전달할 필요를 없애줍니다 — 이른바 prop drilling입니다. John Sundell — Swift by Sundell (2025)의 기사에 따르면, @EnvironmentObject는 교차 화면 데이터(사용자 세션, 앱 설정, 쇼핑 카트 관리자, 로컬 데이터 캐시)에 특히 유용합니다.

핵심 포인트

  • @EnvironmentObject — SwiftUI 환경에서 ObservableObject에 접근하기 위한 property wrapper.
  • .environmentObject()를 통한 주입 — 객체가 계층 구조에 한 번 전달되며 모든 하위 View가 사용 가능.
  • 명시적 전달 없음 — 중간 View가 객체를 알 필요가 없어 아키텍처가 단순화됨.
  • 런타임 크래시 — 환경에서 객체를 찾을 수 없으면 앱이 fatal error와 함께 크래시.
  • iOS 13+ — @EnvironmentObject는 SwiftUI 첫 버전부터 사용 가능.

SwiftUI에서 @EnvironmentObject란

@EnvironmentObject는 SwiftUI View가 애플리케이션 환경에서 ObservableObject에 접근할 수 있게 하는 property wrapper입니다. 환경은 .environmentObject() 수정자를 사용하여 View 계층 구조의 모든 수준에서 객체를 배치할 수 있는 컨테이너입니다. 객체가 환경에 배치되면, 모든 하위 View가 @EnvironmentObject로 속성을 선언하고 객체 유형을 지정하기만 하면 접근할 수 있습니다.

@EnvironmentObject의 주요 목적은 모든 중간 수준을 통해 객체를 전달할 필요 없이 깊은 View 계층 구조를 통해 데이터를 전달하는 문제를 해결하는 것입니다. NavigationStack, TabView 및 모달 창이 분기된 복잡한 애플리케이션에서 @EnvironmentObject는 상용구 코드를 제거하여 아키텍처를 크게 단순화합니다.

Apple Developer Documentation — Environment (2025)에 따르면, @EnvironmentObject는 PreferenceKey 및 View 식별을 기반으로 하는 내부 SwiftUI 메커니즘을 사용합니다. 각 View는 상위 View에서 상속되고 .environmentObject()를 사용하여 확장할 수 있는 자체 환경에 대한 참조를 저장합니다. 객체 검색은 루트 View까지 계층 구조를 위로 올라갑니다.

swift
class UserSession: ObservableObject {
    @Published var isLoggedIn = false
    @Published var userName: String = ""
    
    func login(name: String) {
        userName = name
        isLoggedIn = true
    }
}

@main
struct MyApp: App {
    @StateObject var session = UserSession()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(session)
        }
    }
}

@EnvironmentObject 작동 방식

@EnvironmentObject는 SwiftUI에 내장된 의존성 주입(DI) 메커니즘을 기반으로 작동합니다. View에서 .environmentObject()를 호출하면 SwiftUI는 해당 View와 모든 하위 항목과 연결된 특수 저장소에 객체를 저장합니다. 하위 View가 동일한 유형의 @EnvironmentObject를 선언하면 SwiftUI는 상위 계층 구조를 올라가며 환경에서 객체를 검색합니다.

중요한 특징 — 객체 유형이 환경에서 검색의 키로 사용됩니다. 환경에 동일한 유형의 객체가 두 개 있는 경우 SwiftUI는 계층 구조에서 현재 View에 가장 가까운 객체를 찾습니다. WindowGroup 수준에서 객체가 주입되면 애플리케이션의 모든 화면에서 전역적으로 사용할 수 있게 되어 범용 서비스에 편리합니다.

objc.io — SwiftUI Architecture (2025)에 따르면, 내부적으로 @EnvironmentObject는 @ObservedObject와 유사한 메커니즘을 사용하지만 계층 구조에서 객체를 찾기 위한 추가 추상화 계층이 있습니다. SwiftUI는 객체를 복사하거나 새로 만들지 않습니다 — 기존 인스턴스에 대한 참조를 전달하므로 객체의 변경 사항이 @EnvironmentObject를 사용하는 모든 View에 자동으로 표시됩니다.

환경에서 객체 검색

  • 현재 View에서 위로 — SwiftUI가 현재 View의 환경을 확인한 다음 상위 View, 루트까지 계속 확인.
  • 첫 번째 발견 객체 — 계층 구조를 올라갈 때 발견된 첫 번째 일치 객체가 사용됨.
  • Fatal error — 어떤 수준에서도 객체를 찾을 수 없으면 앱이 “No ObservableObject found”와 함께 크래시.

@EnvironmentObject vs @ObservedObject: 비교

@EnvironmentObject와 @ObservedObject는 모두 동일한 기본 기능을 수행합니다 — ObservableObject의 변경 사항을 View에 구독시킵니다. 차이점은 객체 전달 메커니즘에 있습니다. @ObservedObject는 초기화자를 통한 명시적 전달이 필요하지만, @EnvironmentObject는 각 중간 View에서 명시적으로 지정하지 않고 환경에서 객체를 가져옵니다.

특성@EnvironmentObject@ObservedObject
전달계층 수준에서 .environmentObject()를 통해각 View의 초기화자를 통해
의존성 가시성숨겨짐 — View 시그니처에서 보이지 않음명시적 — View init에서 보임
중간 View객체를 알지 못함객체를 더 전달해야 함
오류 위험객체 누락 시 런타임 크래시컴파일 타임 검사(매개변수가 필수인 경우)
Prop drilling제거수동 전달 필요

@EnvironmentObject와 @ObservedObject 중 선택은 아키텍처에 따라 다릅니다. 객체가 계층 구조 깊숙이 그리고 많은 화면에서 필요한 경우 — @EnvironmentObject가 더 편리합니다. 아키텍처가 테스트 및 가독성을 위해 명시적 의존성 지정을 요구하는 경우 — @ObservedObject가 선호됩니다.

@EnvironmentObject 사용 예제

가장 일반적인 시나리오는 애플리케이션의 모든 화면에서 접근 가능해야 하는 사용자 세션입니다. 애플리케이션 루트에서 .environmentObject()를 통해 UserSession을 주입하면 모든 화면이 사용자 데이터 및 인증 상태에 접근할 수 있습니다.

swift
struct ProfileView: View {
    @EnvironmentObject var session: UserSession
    
    var body: some View {
        VStack {
            if session.isLoggedIn {
                Text("Hello, \(session.userName)")
                Button("Logout") {
                    session.isLoggedIn = false
                }
            } else {
                LoginView()
            }
        }
    }
}

struct SettingsView: View {
    @EnvironmentObject var session: UserSession
    
    var body: some View {
        Form {
            Text("Logged in as \(session.userName)")
        }
    }
}

ProfileView와 SettingsView 모두 초기화자를 통해 세션을 받지 않습니다. 단순히 @EnvironmentObject var session: UserSession을 선언하면 SwiftUI가 자동으로 환경에서 객체를 찾습니다. 이를 통해 기존 데이터 전달 코드를 변경하지 않고 새 화면을 추가할 수 있습니다.

일반적인 실수와 위험

@EnvironmentObject의 주요 위험은 객체가 환경에 주입되지 않은 경우의 런타임 크래시입니다. 선택적 매개변수와 달리 @EnvironmentObject는 nil이 될 수 없습니다. @EnvironmentObject가 있는 View가 화면에 나타나고 상위 View가 해당 유형에 대해 .environmentObject()를 호출하지 않은 경우 앱이 즉시 “Fatal error: No ObservableObject of type X found”와 함께 크래시합니다.

크래시 방지 방법

  • 전역 주입 — 최상위 수준(WindowGroup)에서 객체를 주입하여 모든 화면에서 사용 가능하게.
  • Preview 확인 — SwiftUI Preview에서 항상 .environmentObject()를 추가, 그렇지 않으면 Preview가 크래시.
  • 문서화 및 테스트 — View가 어떤 @EnvironmentObject를 기대하는지 문서화하고 존재를 확인하는 테스트 작성.
  • @ObservedObject로 대체 — 객체가 하나의 화면에만 필요한 경우 명시적 전달로 @ObservedObject 사용.

여러 인스턴스 문제

계층 구조의 다른 수준에서 동일한 유형의 객체를 두 개 주입하면 하위 View는 계층 구조상 가장 가까운 객체를 받습니다. 개발자가 루트 환경의 객체가 동일한 유형의 객체를 가진 자체 환경을 가진 모달 창에서 사용 가능할 것으로 예상하는 경우 혼란을 초래할 수 있습니다.

@EnvironmentObject 대안

SwiftUI의 발전에 따라 @EnvironmentObject의 몇 가지 단점(주로 의존성의 암시성과 런타임 크래시 위험)을 해결하는 대안적 의존성 관리 접근 방식이 등장했습니다.

  • @Environment property wrapper — 내장 환경 값(colorScheme, locale, sizeCategory)용. 사용자 정의 ObservableObject에는 적합하지 않으며 표준 EnvironmentValues 키에만 사용.
  • Custom EnvironmentKey — 값 유형에 대한 사용자 정의 환경 키를 선언할 수 있음. 참조 의미론 때문에 EnvironmentValues에 ObservableObject를 저장하는 것은 권장되지 않음.
  • 명시적 전달의 @ObservedObject — 컴파일 타임 검사가 있는 안전한 접근 방식. 필요한 객체 없이는 View가 나타날 수 없음 — init을 통해 전달되어야 함.
  • 의존성 주입 컨테이너 — SwiftUI 외부에서 의존성을 관리하기 위한 외부 DI 컨테이너(예: Resolver 또는 Swinject).

접근 방식의 선택은 팀 규모와 애플리케이션 복잡성에 따라 다릅니다. 소규모 프로젝트의 경우 @EnvironmentObject가 잘 작동합니다. 수십 개의 화면과 엄격한 테스트 요구 사항이 있는 대규모 프로젝트의 경우 @ObservedObject 또는 DI 컨테이너를 통한 명시적 전달이 선호됩니다.

자주 묻는 질문

하나의 View에서 여러 @EnvironmentObject를 사용할 수 있나요?

네, View는 필요한 만큼 다양한 유형의 @EnvironmentObject를 선언할 수 있습니다. SwiftUI는 각 유형을 환경에서 독립적으로 검색합니다. 이는 View가 사용자 세션, 설정 및 쇼핑 카트에 동시에 접근해야 할 때 유용합니다 — 각 객체는 별도로 주입됩니다.

.environmentObject() 없이 Preview에 @EnvironmentObject를 주입하면 어떻게 되나요?

View를 표시하려고 하면 Preview가 런타임 오류와 함께 크래시합니다. @EnvironmentObject를 사용하는 View의 경우 Preview에 항상 .environmentObject()를 추가하세요. 테스트 데이터가 있는 모의 객체를 사용하여 Preview가 올바르게 작동하고 현실적인 상태를 표시하도록 합니다.

프로토콜과 함께 @EnvironmentObject를 사용할 수 있나요?

아니요, @EnvironmentObject는 ObservableObject를 준수하는 구체적인 클래스 유형에서만 작동합니다. 프로토콜의 경우 type erasure 또는 래퍼를 사용해야 합니다: 프로토콜 유형 객체에 대한 참조를 보유하는 래퍼 클래스를 만들고 @EnvironmentObject를 통해 래퍼를 주입합니다.

@EnvironmentObject를 사용하는 View를 테스트하려면 어떻게 해야 하나요?

테스트 데이터가 있는 ObservableObject 인스턴스를 만들고 테스트에서 .environmentObject(testObject)를 통해 View에 전달합니다. 이것이 SwiftUI UI 테스트의 표준 패턴입니다. 단위 테스트의 경우 로직을 ObservableObject로 분리하고 View와 별도로 테스트합니다.

@EnvironmentObject는 화면이 많을 때 성능에 영향을 미치나요?

@EnvironmentObject는 객체의 참조만 전달하고 복사하지 않기 때문에 추가 성능 오버헤드를 만들지 않습니다. 그러나 전역 객체의 @Published 속성이 자주 업데이트되면 많은 View가 동시에 다시 렌더링되어 성능에 영향을 줄 수 있습니다.

요약

  • @EnvironmentObject — 초기화자를 통한 명시적 전달 없이 SwiftUI 환경에서 ObservableObject에 접근하기 위한 property wrapper.
  • .environmentObject()를 통한 주입 — 객체가 특정 계층 수준에서 환경에 배치됨.
  • 자동 검색 — SwiftUI가 유형을 키로 사용하여 계층 구조를 위로 올라가며 객체 검색.
  • 런타임 크래시 — 객체를 찾을 수 없으면 앱이 fatal error와 함께 크래시하므로 주의 필요.
  • Prop drilling 해결 — @EnvironmentObject가 중간 View를 통한 데이터 전달 필요성 제거.
  • 암시적 의존성 — 의존성이 View 시그니처에 보이지 않아 코드 이해가 어려워짐.
  • 대안 — 명시적 전달에는 @ObservedObject, 대규모 프로젝트에는 DI 컨테이너.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기