[SwiftUI] ํ—ท๊น”๋ฆฌ๋Š” State, Binding, ObservedObject, EnvironmentObject ์ด์ •๋ฆฌ

    SwiftUI์—์„œ์˜ Single Source of Truth(SSOT, ๋‹จ์ผ ์ง„์‹ค ๊ณต๊ธ‰์›)๋ž€ ๋ฐ์ดํ„ฐ์˜ ์ผ๊ด€์„ฑ๊ณผ ์ •ํ™•์„ฑ์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ์ค‘์š”ํ•œ ๊ฐœ๋…์ด๋‹ค.

    ์ •๋ณด ์‹œ์Šคํ…œ์— ๋Œ€ํ•œ SSOT(Single Source Of Truth) ์•„ํ‚คํ…์ฒ˜ ๋˜๋Š” SPOT(Single Point Of Truth) ์•„ํ‚คํ…์ฒ˜๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ ์š”์†Œ๊ฐ€ ๋งˆ์Šคํ„ฐ(๋˜๋Š” ํŽธ์ง‘)๋˜๋„๋ก ์ •๋ณด ๋ฐ ๋ชจ๋ธ ๊ด€๋ จ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๊ด€ํ–‰์ด๋‹ค. ํ•œ ๊ณณ์—์„œ๋งŒ ์ •๊ทœ ํ˜•์‹์œผ๋กœ ๋ฐ์ดํ„ฐ ์ •๊ทœํ™”๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ์ด ๋ฐ์ดํ„ฐ ์š”์†Œ์— ๋Œ€ํ•œ ๋ชจ๋“  ๊ฐ€๋Šฅํ•œ ์—ฐ๊ฒฐ์€ ์ฐธ์กฐ์šฉ์ด๋‹ค. ๋ฐ์ดํ„ฐ์˜ ๋‹ค๋ฅธ ๋ชจ๋“  ์œ„์น˜๋Š” "source of truth" ์œ„์น˜๋ฅผ ๋‹ค์‹œ ์ฐธ์กฐํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ๋ณธ ์œ„์น˜์˜ ๋ฐ์ดํ„ฐ ์š”์†Œ์— ๋Œ€ํ•œ ์—…๋ฐ์ดํŠธ๋Š” ์ „์ฒด ์‹œ์Šคํ…œ์— ์ „ํŒŒ๋˜์–ด ํšจ์œจ์„ฑ/์ƒ์‚ฐ์„ฑ ํ–ฅ์ƒ, ์ž˜๋ชป๋œ ๋ถˆ์ผ์น˜์˜ ์‰ฌ์šด ๋ฐฉ์ง€ ๊ฐ™์€ ์—ฌ๋Ÿฌ ์ด์ ์„ ๋™์‹œ์— ์ œ๊ณตํ•œ๋‹ค. SSOT ์•„ํ‚คํ…์ฒ˜๊ฐ€ ์—†์œผ๋ฉด ๋ช…ํ™•์„ฑ, ์ƒ์‚ฐ์„ฑ์„ ์†์ƒ์‹œ์ผœ ์œ ์ง€๊ด€๋ฆฌ๊ฐ€ ํž˜๋“ค์–ด์ง„๋‹ค

     

     

    SwiftUI ์•ฑ์—์„œ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค(UI)๋Š” ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์— ๋ฐ”์ธ๋”ฉ๋˜์–ด ์žˆ๋‹ค. ์ฆ‰, SwiftUI๋Š” UI๊ฐ€ @State์™€ ๊ฐ™์€ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์— ๋ฐ”์ธ๋”ฉ๋˜์–ด ์žˆ์–ด UI๋Š” ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์˜ ๋ณ€๊ฒฝ์— ์ž๋™์œผ๋กœ ๋ฐ˜์‘ํ•˜๊ณ  ๋ณ€๊ฒฝ๋œ๋‹ค. ํ•˜์ง€๋งŒ UI๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ์ƒํƒœ๊ฐ€ ์—ฌ๋Ÿฌ ๊ณณ์—์„œ ๋ณต์‚ฌ๋˜๊ณ  ๋ณ€๊ฒฝ๋˜๊ณ  ์‚ฌ์šฉ๋˜๋ฉด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜(UX)์˜ ์ผ๊ด€์„ฑ๊ณผ ์ •ํ™•์„ฑ์„ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ด ์–ด๋ ค์›Œ์ง„๋‹ค. 

    ๋”ฐ๋ผ์„œ SwiftUI์—์„œ๋Š” ์ฃผ๋กœ @State, @Binding, @ObservedObject, @EnvironmentObject์™€ ๊ฐ™์€ ์†์„ฑ ๋ž˜ํผ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์„ ๊ด€๋ฆฌํ•œ๋‹ค.

     

    • @State: ๊ฐ’ ์œ ํ˜•์˜ ์†์„ฑ์— ๋Œ€ํ•œ ์ €์žฅ์†Œ๋กœ ์‚ฌ์šฉ
    • @Binding: ๋‘ ๊ฐœ์˜ ๋ทฐ ๊ฐ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๊ณ  ๋™๊ธฐํ™”ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ
    • @ObservedObject: ๊ด€์ฐฐ ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ
    • @EnvironmentObject: ์•ฑ์˜ ์ „์—ญ ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๋ฐ ์‚ฌ์šฉ

     

    ์•„๋ž˜ ์˜ˆ์‹œ ์ฝ”๋“œ๋Š” https://www.youtube.com/watch?v=taoKnvqFy7k ์˜ ์ฝ”๋“œ์—

    ๋‚ด๊ฐ€ ObservedObject, EnvironmentObject๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์—ฐ์Šตํ•œ ์ฝ”๋“œ๋‹ค!

     

    // ContentView.swift
    
    import SwiftUI
    
    // ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” UserData ํด๋ž˜์Šค
    class UserData: ObservableObject {
        @Published var name: String = ""
    }
    
    struct ContentView: View {
        @State private var isDestinationPresented = false //@State๋ฅผ ํ†ตํ•ด์„œ ํ•˜๋‚˜์˜ single source of truth๊ฐ€ ์ƒ์„ฑ
        @EnvironmentObject var userData: UserData // ์ „์—ญ์ ์œผ๋กœ ๊ณต์œ ๋˜๋Š” UserData ๊ฐ์ฒด์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•œ ํ™˜๊ฒฝ ๊ฐ์ฒด
        
        var body: some View {
            VStack {
                // ์‚ฌ์šฉ์ž ์ด๋ฆ„์„ ์ž…๋ ฅ๋ฐ›๋Š” ํ…์ŠคํŠธ ํ•„๋“œ
                TextField("Enter your name", text: $userData.name)
                    .textFieldStyle(.roundedBorder)
                    .padding()
                
                // ์ž…๋ ฅ๋œ ์‚ฌ์šฉ์ž ์ด๋ฆ„์„ ํ‘œ์‹œํ•˜๋Š” ํ…์ŠคํŠธ
                Text("Hello, \(userData.name)!")
                    .font(.title)
                
                Text("Click here!")
                    .onTapGesture {
                        self.isDestinationPresented.toggle()
                    }
                    .sheet(isPresented: $isDestinationPresented) {
                        DestinationView(isDestinationPresented: self.$isDestinationPresented)
                    }
            }
        }
    }
    
    struct DestinationView: View {
        @EnvironmentObject var userData: UserData // ์ „์—ญ์ ์œผ๋กœ ๊ณต์œ ๋˜๋Š” UserData ๊ฐ์ฒด์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•œ ํ™˜๊ฒฝ ๊ฐ์ฒด
        @Binding var isDestinationPresented: Bool
        
        var body: some View {
            VStack {
                Text("Another View")
                    .font(.title)
                
                // ์ „์—ญ UserData ๊ฐ์ฒด์˜ ์ด๋ฆ„์„ ํ‘œ์‹œํ•˜๋Š” ํ…์ŠคํŠธ
                Text("Hello, \(userData.name)!")
                    .font(.headline)
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    // SourceOfTruthApp.swift
    
    import SwiftUI
    
    @main
    struct SourceOfTruthApp: App {
        @StateObject var userData = UserData() // ์•ฑ์˜ ์ „์—ญ ์ƒํƒœ๋กœ ์‚ฌ์šฉ๋˜๋Š” UserData ๊ฐ์ฒด
        var body: some Scene {
            WindowGroup {
                ContentView()
                    .environmentObject(userData) // ContentView์— ์ „์—ญ UserData ๊ฐ์ฒด๋ฅผ ์ฃผ์ž…ํ•˜์—ฌ ๊ณต์œ 
            }
        }
    }

     

     

    ์œ„์˜ ์ฝ”๋“œ๋Š” ContentView์™€ DestinationView ๋‘ ๊ฐœ์˜ ๋ทฐ๊ฐ€ ์žˆ๋‹ค.
    UserData ํด๋ž˜์Šค๋Š” ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ํด๋ž˜์Šค๋กœ ObservableObject ํ”„๋กœํ† ์ฝœ์„ ์ฑ„ํƒํ•˜๊ณ , @Published ์†์„ฑ ๋ž˜ํผ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ name ์†์„ฑ์„ ๊ฐ์‹œ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ ๋‹ค.
    ContentView๋Š” ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์ด๋ฆ„์„ ์ž…๋ ฅ๋ฐ›๋Š” ํ…์ŠคํŠธ ํ•„๋“œ์™€ ์ž…๋ ฅ๋œ ์ด๋ฆ„์„ ํ‘œ์‹œํ•˜๋Š” ํ…์ŠคํŠธ๊ฐ€ ํฌํ•จ๋œ ๋ทฐ๋‹ค. @EnvironmentObject ์†์„ฑ ๋ž˜ํผ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ „์—ญ์ ์œผ๋กœ ๊ณต์œ ๋˜๋Š” UserData ๊ฐ์ฒด์— ์ ‘๊ทผํ•˜๋ฉฐ, ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜๋ฉด ํ•ด๋‹น ๊ฐ์ฒด์˜ name ์†์„ฑ์ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ์—…๋ฐ์ดํŠธ๋œ๋‹ค. (์˜์ƒ ์ฐธ๊ณ !) 

    DestinationView๋Š” ๋‹ค๋ฅธ ๋ทฐ๋กœ ์ด๋™ํ•˜๋Š” ๋ฐ๋ชจ์šฉ ๋ทฐ๋กœ, UserData ๊ฐ์ฒด์˜ name ์†์„ฑ์„ ํ‘œ์‹œํ•œ๋‹ค. @EnvironmentObject ์†์„ฑ ๋ž˜ํผ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ „์—ญ์ ์œผ๋กœ ๊ณต์œ ๋˜๋Š” UserData ๊ฐ์ฒด์— ์ ‘๊ทผํ•œ๋‹ค.

    SourceOfTruthApp์€ ์•ฑ์˜ ์ง„์ž…์ ์œผ๋กœ, @StateObject ์†์„ฑ ๋ž˜ํผ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ „์—ญ์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” UserData ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ContentView์— environmentObject๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ UserData ๊ฐ์ฒด๋ฅผ ์ฃผ์ž…ํ•˜์—ฌ ๋ทฐ ๊ณ„์ธต ๊ตฌ์กฐ์—์„œ ๊ณต์œ ๋  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

    ๊ผญ ContentView ๋ฅผ ๊ฐ์‹ธ๋Š” struct ๋‚ด์—์„œ StateObject๋กœ ๊ฐ์ฒด๋ฅผ ์„ ์–ธํ•˜๊ณ ,  .environmentObject(userData) ๋ฅผ ๋ถ™์—ฌ์„œ ์ „์—ญ UserData ๊ฐ์ฒด๋ฅผ ์ฃผ์ž…ํ•˜์—ฌ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ContentView์™€ DestinationView๊ฐ€ ๋™์ผํ•œ UserData ๊ฐ์ฒด๋ฅผ ๊ณต์œ ํ•˜๊ณ , name ์†์„ฑ์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

     

    ๋ฐ˜์‘ํ˜•

    ๋Œ“๊ธ€