@EnvironmentObject는 SwiftUI의 property wrapper로, 계층 구조의 모든 View가 초기화자 체인을 통해 명시적으로 전달하지 않고 ObservableObject에 접근할 수 있게 합니다. 객체는 .environmentObject() 수정자를 사용하여 계층 구조의 특정 수준에서 환경에 주입되며, 이후 모든 하위 View가 @EnvironmentObject를 통해 접근할 수 있습니다. 이는 객체를 사용하지 않는 중간 View를 통해 객체를 전달할 필요를 없애줍니다 — 이른바 prop drilling입니다. John Sundell — Swift by Sundell (2025)의 기사에 따르면, @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까지 계층 구조를 위로 올라갑니다.
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는 SwiftUI에 내장된 의존성 주입(DI) 메커니즘을 기반으로 작동합니다. View에서 .environmentObject()를 호출하면 SwiftUI는 해당 View와 모든 하위 항목과 연결된 특수 저장소에 객체를 저장합니다. 하위 View가 동일한 유형의 @EnvironmentObject를 선언하면 SwiftUI는 상위 계층 구조를 올라가며 환경에서 객체를 검색합니다.
중요한 특징 — 객체 유형이 환경에서 검색의 키로 사용됩니다. 환경에 동일한 유형의 객체가 두 개 있는 경우 SwiftUI는 계층 구조에서 현재 View에 가장 가까운 객체를 찾습니다. WindowGroup 수준에서 객체가 주입되면 애플리케이션의 모든 화면에서 전역적으로 사용할 수 있게 되어 범용 서비스에 편리합니다.
objc.io — SwiftUI Architecture (2025)에 따르면, 내부적으로 @EnvironmentObject는 @ObservedObject와 유사한 메커니즘을 사용하지만 계층 구조에서 객체를 찾기 위한 추가 추상화 계층이 있습니다. SwiftUI는 객체를 복사하거나 새로 만들지 않습니다 — 기존 인스턴스에 대한 참조를 전달하므로 객체의 변경 사항이 @EnvironmentObject를 사용하는 모든 View에 자동으로 표시됩니다.
@EnvironmentObject와 @ObservedObject는 모두 동일한 기본 기능을 수행합니다 — ObservableObject의 변경 사항을 View에 구독시킵니다. 차이점은 객체 전달 메커니즘에 있습니다. @ObservedObject는 초기화자를 통한 명시적 전달이 필요하지만, @EnvironmentObject는 각 중간 View에서 명시적으로 지정하지 않고 환경에서 객체를 가져옵니다.
| 특성 | @EnvironmentObject | @ObservedObject |
|---|---|---|
| 전달 | 계층 수준에서 .environmentObject()를 통해 | 각 View의 초기화자를 통해 |
| 의존성 가시성 | 숨겨짐 — View 시그니처에서 보이지 않음 | 명시적 — View init에서 보임 |
| 중간 View | 객체를 알지 못함 | 객체를 더 전달해야 함 |
| 오류 위험 | 객체 누락 시 런타임 크래시 | 컴파일 타임 검사(매개변수가 필수인 경우) |
| Prop drilling | 제거 | 수동 전달 필요 |
@EnvironmentObject와 @ObservedObject 중 선택은 아키텍처에 따라 다릅니다. 객체가 계층 구조 깊숙이 그리고 많은 화면에서 필요한 경우 — @EnvironmentObject가 더 편리합니다. 아키텍처가 테스트 및 가독성을 위해 명시적 의존성 지정을 요구하는 경우 — @ObservedObject가 선호됩니다.
가장 일반적인 시나리오는 애플리케이션의 모든 화면에서 접근 가능해야 하는 사용자 세션입니다. 애플리케이션 루트에서 .environmentObject()를 통해 UserSession을 주입하면 모든 화면이 사용자 데이터 및 인증 상태에 접근할 수 있습니다.
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”와 함께 크래시합니다.
계층 구조의 다른 수준에서 동일한 유형의 객체를 두 개 주입하면 하위 View는 계층 구조상 가장 가까운 객체를 받습니다. 개발자가 루트 환경의 객체가 동일한 유형의 객체를 가진 자체 환경을 가진 모달 창에서 사용 가능할 것으로 예상하는 경우 혼란을 초래할 수 있습니다.
SwiftUI의 발전에 따라 @EnvironmentObject의 몇 가지 단점(주로 의존성의 암시성과 런타임 크래시 위험)을 해결하는 대안적 의존성 관리 접근 방식이 등장했습니다.
접근 방식의 선택은 팀 규모와 애플리케이션 복잡성에 따라 다릅니다. 소규모 프로젝트의 경우 @EnvironmentObject가 잘 작동합니다. 수십 개의 화면과 엄격한 테스트 요구 사항이 있는 대규모 프로젝트의 경우 @ObservedObject 또는 DI 컨테이너를 통한 명시적 전달이 선호됩니다.
자주 묻는 질문
네, View는 필요한 만큼 다양한 유형의 @EnvironmentObject를 선언할 수 있습니다. SwiftUI는 각 유형을 환경에서 독립적으로 검색합니다. 이는 View가 사용자 세션, 설정 및 쇼핑 카트에 동시에 접근해야 할 때 유용합니다 — 각 객체는 별도로 주입됩니다.
View를 표시하려고 하면 Preview가 런타임 오류와 함께 크래시합니다. @EnvironmentObject를 사용하는 View의 경우 Preview에 항상 .environmentObject()를 추가하세요. 테스트 데이터가 있는 모의 객체를 사용하여 Preview가 올바르게 작동하고 현실적인 상태를 표시하도록 합니다.
아니요, @EnvironmentObject는 ObservableObject를 준수하는 구체적인 클래스 유형에서만 작동합니다. 프로토콜의 경우 type erasure 또는 래퍼를 사용해야 합니다: 프로토콜 유형 객체에 대한 참조를 보유하는 래퍼 클래스를 만들고 @EnvironmentObject를 통해 래퍼를 주입합니다.
테스트 데이터가 있는 ObservableObject 인스턴스를 만들고 테스트에서 .environmentObject(testObject)를 통해 View에 전달합니다. 이것이 SwiftUI UI 테스트의 표준 패턴입니다. 단위 테스트의 경우 로직을 ObservableObject로 분리하고 View와 별도로 테스트합니다.
@EnvironmentObject는 객체의 참조만 전달하고 복사하지 않기 때문에 추가 성능 오버헤드를 만들지 않습니다. 그러나 전역 객체의 @Published 속성이 자주 업데이트되면 많은 View가 동시에 다시 렌더링되어 성능에 영향을 줄 수 있습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.