๐Ÿ“ฑ App/๐ŸŽ‡ Swift

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

chamroro 2023. 5. 31. 11:25

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 ์†์„ฑ์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

 

๋ฐ˜์‘ํ˜•