Core Data를 View에서 사용하기 위해, managedObjectContext
라는 Environment를 지정해주자.
... 뭐라고?
정말 무슨 말인지 하나도 모르겠기 때문에, 하나하나 뜯어보자.
우선 SwiftUI에서 Environment
라는 것은 Property Wrapper 라는 특수한 형태의 구조체이다. 구조체라는 말은 여러 프로퍼티 값들을 갖고 있다는 말인데, Environment
는 말 그대로 View의 "환경"과 관련된 여러 값들을 갖고있다. 예를 들면, colorScheme
, pixelLength
, locale
과 같은 것들이다. 모든 리스트는 EnvironmentValues 문서를 참조하자.
React 에서 온 개발자들이라면, Redux 같은 글로벌 스테이트 관리를 생각해보면 된다. 실제로 React 앱을 개발할 때, Color Scheme이나 Locale 같은 건 Redux 등으로 앱 전반에 걸친 글로벌 스테이트로 넣고 관리하기도 하는데, 같은 개념으로 이해하면 된다.
다시 돌아와서, Environment
는 View 와 관련된 여러 변수들을 View 계층 구조에 전반에 걸쳐 보급하는 역할을 한다. 그래서 상위 View 에서 environment()
함수를 통해 Environment Value를 지정해주면, 하위 View에서 적당히 꺼내 쓸 수 있다. 이때, Environment Value 내용이 변경 되면, 자동으로 그 변경이 전파된다.
좋다. 그럼 Property Wrapper 는 무엇인가.
Property Wrapper 는 프로퍼티의 반복적인 get
, set
행위를 재사용하기 위한 구조체이다. ... 이게 무슨 말인가 알아보기 위해, 공식 문서의 Property Wrapper 예제를 살펴보자. (링크로 가서 적당히 내려보면 Property Wrapper 섹션이 있다. 섹션 링크도 제공하지 않다니... 실망이야)
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
TwelveOrLess
라는 Property Wrapper 구조체는 Int
타입의 프로퍼티에 대한 get
, set
에 대한 아래와 같은 행위를 'Wrapping' 하고 있다.
get
: 그냥 같은 값을 반환set
: 입력값이 12보다 크면, 12를 할당. 아니면 입력값을 할당.즉, TwelveOrLess
로 "Wrapping" 된 Int
타입의 프로퍼티는 얼마의 값이 할당되던 간에 최대 12까지만 갖을 수 있다.
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}
var rectangle = SmallRectangle()
print(rectangle.height)
// Prints "0"
rectangle.height = 10
print(rectangle.height)
// Prints "10"
rectangle.height = 24
print(rectangle.height)
// Prints "12"
Property Wrapper 는 프로퍼티의 get
, set
행위 중 반복적으로 사용하는 행위를 구조체 형태로 정의하여, 여기저기 재사용할 수 있도록 해주는데 있다. 만약 이런 문법이 없다면, "최대 12 까지만 할당될 수 있음"에 해당하는 set
코드를 height
와 width
각각에 중복해서 작성해주어야 했을 것이다.
자 이제 다시 처음으로 돌아와서, Core Data를 View에서 사용하기 위해, managedObjectContext
라는 Environment를 지정해주자. 여기서 Environment
는 Property Wrapper 이다.
@main
struct DoneListApp: App {
let persistentController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistentController.container.viewContext)
}
}
}
Environment
의 EnvironmentValues
는 여러가지가 있지만, 우리가 사용할 것은 managedObjectContext
로 NSPersistentContainer
의 viewContext
값을 할당해주면 된다. colorScheme
등과는 다르게, managedObjectContext
는 우리가 외부에서 별도로 주입해주어야 하는데, 이를 위한 함수가 View
구조체의 environment
함수다.
ContentView().environment(\.managedObjectContext, persistentController.container.viewContext)
여기서 \.managedObjectContext
는 EnvironmentValues
구조체의 managedObjectContext
프로퍼티를 의미한다. ...어째서냐고? 이게 바로 Key Path Expression 이라는 문법이다. (하아...)
Key Path Expression은 런타임 중 특정 타입의 객체가 파라미터로 들어왔을 때, 해당 타입의 특정 프로퍼티에 대한 경로를 표현해주는 것을 의미한다.
즉, 우리가 명시적으로 "어떤 변수(객체)"의 "이 프로퍼티"를 써주세요 하고 코드에 입력할 수 있을 것이고, 이게 보통의 코딩이다. 하지만, 코드를 작성하는 순간에는 어떤 객체(변수)가 들어올지 모를 경우, 프로퍼티로 향하는 경로만 입력해줌으로써, 런타임 중 어떤 객체가 들어오던지같에 타입만 맞으면 그 특정 프로퍼티를 꺼내서 쓸 수 있게 해주는 것이다.
위 코드에서 \.managedObjectContext
는 사실 \EnvironmentValues.managedObjectContext
의 축약이다. EnvironmentValues
타입에 대해 타입 인퍼런스가 가능해서 생략된 것이다. 즉, 위 코드는 EnvironmentValues
타입의 객체가 일단 들어오면, 함수에서는 .managedObjectContext
에 있는 프로퍼티 값을 쓰라는 의미다.
다시 돌아와서, environment(_ keyPath:, _ value:)
함수는 해당 keyPath
에 해당 value
를 set
하라는 의미이다. 그래서 내가 만든 NSPersistentContainer
타입 객체의 viewContext
값을 EnvironmentValues.managedObjectContext
에 할당하게 된다.
struct ContentView: View {
@Environment(\.managedObjectContext) var context
...
}
하위 View 들에서는 위 코드와 같이 @Environment
를 통해, 내가 주입한 값을 꺼내 쓸 수 있다.