[SwiftUI] 프로퍼티 래퍼(Property Wrapper)

ko_hyeji·2024년 3월 28일
0

@State

  • SwiftUI에서 상태를 처리하는 방법
  • 뷰의 상태를 저장하는 프로퍼티로, 해당 뷰의 상태를 관리함
  • 기본적으로 Private 선언이기에 다른 뷰와 값을 소통하려면 Binding을 이용함
  • 값이 변경될 때마다 UI가 업데이트 됨
struct ContentView: View {
	@State private var isPlaying: Bool = false
	
	var body: some View {
		Button(isPlaying ? "Pause" : "Play") {
			isPlaying.toggle()
		}
	}
}

@Binding

  • 뷰와 상태를 바인딩 하는 방법
  • 상위 @State 변수를 전달 받아 하위 뷰에서 변화를 감지하고 연결함
  • Binding은 다른 뷰가 소유한 속성을 연결하기 때문에 소유권 및 저장 공간이 없음
struct PlayerView: View {
  var episode: Episode
  @State private var isPlaying: Bool = false
  
  var body: some View {
    VStack {
      Text(episode.title)
        .foregroundStyle(isPlaying ? .primary : .secondary)
      PlayButton(isPlaying: $isPlaying) // Binding
    }
  }
}

struct PlayButton: View {
  @Binding var isPlaying: Bool
  
  var body: some View {
    Button(isPlaying ? "Pause" : "Play") {
      isPlaying.toggle()
    }
  }
}

PlayButton(isPlaying: $isPlaying)
$: State 변수의 참조를 생성하여 해당 변수의 상태를 다른 뷰나 속성과 양방향으로 Binding 할 수 있도록 도와줌

ObservableObject

ObservableObject | Apple Developer Documentation

💡 PropertyWrapper가 아닌 클래스 프로토콜!

  • 객체가 변경되기 전에 그 변경 사항을 방출하는 publisher를 가지고 있는 객체 유형
    ObservableObject는 기본적으로 Published 프로퍼티의 값이 변경되기 전에 변경된 값을 방출하는 objectWillChange publisher를 가지고 있음)
  • 뷰에서 인스턴스의 변화를 감시하기 위해 뷰모델 객체를 생성할 때 주로 사용됨
class Contact: ObservableObject {
  @Published var name: String
  @Published var age: Int
  
  init(name: String, age: Int) {
    self.name = name
    self.age = age
  }
  
  func haveBirthday() -> Int {
    age += 1
    return age
  }
}

let john = Contact(name: "John Appleseed", age: 24)
let cancellable = john.objectWillChange
  .sink { _ in
    print("\(john.age) will change") // 24 will change
  }
print(john.haveBirthday()) // 25

@Published

  • ObservableObject를 구현한 클래스 내에서 프로퍼티 선언 시 사용
  • @Published로 선언된 프로퍼티를 뷰에서 관찰할 수 있음
  • ObservableObject의 objectWillChange.send() 메서드를 @Published 프로퍼티가 변경되면 자동으로 호출함

@ObservedObject

  • 뷰에서 ObservableObject 타입의 인스턴스 선언 시 사용
  • ObservableObject의 값이 업데이트되면 뷰를 업데이트함
class User: ObservableObject {
  @Published var age = 10
}

struct ContentView: View {
  @ObservedObject var user: User
  
  var body: some View {
    Button("Plus Age") {
      user.age += 1
    }
  }
}

@StateObject

  • 뷰에서 ObservableObject 타입의 인스턴스 선언 시 사용
  • 뷰마다 하나의 인스턴스를 생성하며, 뷰가 사라지기 전까지 같은 인스턴스를 유지함
  • @ObservedObject의 뷰 렌더링 시 인스턴스 초기화 이슈를 해결하기 위한 방법으로 사용됨
  • 매번 인스턴스가 새롭게 생성되는 것처럼 외부에서 주입 받는 경우가 아닌 최초 생성 선언 시에 @StateObject를 사용하는 것이 적절한 방법

@StateObject vs @ObservedObject

▪︎ 인스턴스를 최초로 생성하는 경우 → @StateObject
▪︎ 하위 뷰에서 주입받는 경우 → @ObservedObject


@Environment

  • 미리 정의되어 있는 시스템의 공유 데이터
  • 사용하려는 공유 데이터의 이름을 keyPath로 전달하여 사용함
  • 시스템 공유 데이터는 가변하기에 var로 선언해야 함
  • 뷰가 생성되는 시점에 값이 자동으로 초기화됨
struct ContentView: View {
  @Environment(\.colorScheme) var colorScheme
  
  var body: some View {
    Text("Hello, World!")
      .foregroundStyle(colorScheme == .dark ? .white : .black)
  }
}

@EnvironmentObject

  • ObservableObject를 통해 구현된 타입의 인스턴스를 전역적으로 공유하여 사용함
  • 앱 전역에서 공통으로 사용할 데이터를 주입 및 사용
class Info: ObservableObject {
  @Published var age = 10
}

@main
struct MyApp: App {
  var body: some Scene {
    WindowGroup {
      MainView()
        .environmentObject(Info())
    }
  }
}

struct MainView: View {
  @EnvironmentObject var info: Info
  
  var body: some View {
    Button {
      self.info.age += 1
    } label: {
      Text("Click Me for plus age")
    }
    SubView()
  }
}

struct SubView: View {
  @EnvironmentObject var info: Info
  
  var body: some View {
    Button {
      self.info.age -= 1
    } label: {
      Text("Click Me for minus age")
    }
  }
}

⚠️ @EnvironmentObject 사용의 문제점

1. 데이터 의존성 문제 발생: 해당 객체에서 의존성이 앱 전체로 퍼질 수 있어 코드 가독성과 유지 보수가 어려워지고, 예기치 않은 버그를 유발할 수 있음.

2. 데이터 관리의 복잡도 증가: 여러 뷰에서 동일한 EnvironmentObject를 참조하고 수정하면 데이터의 일관성을 유지하기가 힘들어짐.

3. 테스트의 어려움: EnvironmentObject에 의존하면 단위 테스트나 UI 테스트를 작성하기 어려워짐. (각 테스트케이스마다 필요한 환경객체들을 설정해줘야 하는데 이는 오버헤드를 초래하게 됨.)

최대한 구제적인 범위 안에서는 @State, @Binding, @ObservedObject를 사용하고 정말 필요할 때, 전역적으로 공유되어야 하는 상태에서만 @EnvironmentObject를 사용하는 것이 바람직함.

profile
iOS Developer

0개의 댓글