[Swift] @ObservedObject와 @StateObject의 차이에 대해 알아보기(Property Wrapper 2편)

2024. 1. 20. 16:07·iOS&Swift

안녕하세요.

이전 글에선 @State, @Binding, @Published 3가지 Property Wrapper에 대해 알아보았고,

이번엔 @ObservedObject, @StateObject에 대해 알아보겠습니다.

@ObservedObject와 @StateObject

A property wrapper type that subscribes to an observable object and invalidates a view whenever the observable object changes.
출처: 공식문서

둘은 비슷한 상황에 사용되고, 공통점이 많기 때문에 같이 정리해 보겠습니다.

@ObservedObject와 @StateObject는 ViewModel 객체와 같은 반응형 객체를 만들 때 사용되는 Property Wrapper입니다.

두 Property Wrapper를 사용하려면, 해당 객체는 아래와 같이 ObservableObject 프로토콜을 준수하는 클래스 객체여야 합니다.

class DataModel: ObservableObject {
    @Published var name = "Some Name"
}

@ObservedObject와 @StateObject는 해당 객체를 SwiftUI에서 View가 관찰 가능한 객체로 만들어 주며, 데이터 변경에 따른 UI를 다시 그릴 수 있게 해줍니다.

ObservableObject 객체는 View가 변경에 감지해야 할 프로퍼티를 이전에 다뤘던 @Published Property Wrapper를 통해 선언해 줍니다.

또는, @Published로 선언하지 않고, 아래와 같이

class DataModel: ObservableObject {
    var name = "Some Name"
    
    func changeName(name: String) {
        self.name = name
        objectWillChange.send()
    }
}

objectWillChange.send() 메서드를 호출해 ObservableObject 객체의 상태값이 변경되었다는 것을 수동으로 View에게 전달할 수 있습니다.(기본적으로 @Published 사용함.)

그럼 차이점은 무엇일까?

@ObservedObject와 @StateObject의 차이점

@ObservedObject는 아래와 같이 View 객체 내부에서 인스턴스화할 경우 한 가지 문제가 발생합니다.

@ObservedObject로 선언한 객체의 경우 해당 View가 그려질 때 같이 초기화됩니다.

코드를 통해 확인해 보면,

struct SubView: View {
    @ObservedObject private var dataModel = DataModel()
    var body: some View {
        VStack {
            Text("이름: \(dataModel.name)")
            Button("랜덤 이름") {
                dataModel.changeName(name: "ABC\(Int.random(in: 1...1000))")
            }
        }
    }
}

class DataModel: ObservableObject {
    @Published private(set) var name = "Some Name"
    
    func changeName(name: String) {
        self.name = name
    }
}

SubView가 있고, 이름을 데이터를 관리하는 @ObservedObject로 선언된 DataModel 객체가 있습니다.

struct ContentView: View {
    @State private var code: Int = 0
    
    var body: some View {
        VStack {
            Text("코드: \(code)")
            SubView()
            Button("랜덤 코드") {
                code = Int.random(in: 1...1000)
            }
        }
        .padding()
    }
}

ContentView 내부에는 위에서 만든 SubView가 있고, 코드값을 보여주는 TextView와 코드를 변경해주는 Button이 있습니다.

이름을 변경하다가 코드가 변경되면 이름값이 초기화되는 것을 확인할 수 있습니다.

위와 같은 문제를 해결하기 위해 2가지 방법이 있는데

  • @ObservedObject 객체를 부모 뷰에서 주입시켜주는 방법
  • @StateObject를 사용하는 방법

첫번째를 해보면, 

struct ContentView: View {
    @State private var code: Int = 0
    
    @ObservedObject private var dataModel = DataModel()
    var body: some View {
        VStack {
            Text("코드: \(code)")
            SubView(dataModel: dataModel)
            Button("랜덤 코드") {
                code = Int.random(in: 1...1000)
            }
        }
        .padding()
    }
}

struct SubView: View {
    @ObservedObject private var dataModel: DataModel
    
    init(dataModel: DataModel) {
        self.dataModel = dataModel
    }
    
    var body: some View {
        VStack {
            Text("이름: \(dataModel.name)")
            Button("랜덤 이름") {
                dataModel.changeName(name: "ABC\(Int.random(in: 1...1000))")
            }
        }
    }
}

위와 같이 DataModel을 부모뷰로 부터 주입받도록 수정했습니다.

위와 같이 코드가 변경될 때 이름이 초기화되지 않습니다.

두 번째도 해보면,

struct SubView: View {
    @StateObject private var dataModel = DataModel()
    var body: some View {
        VStack {
            Text("이름: \(dataModel.name)")
            Button("랜덤 이름") {
                dataModel.changeName(name: "ABC\(Int.random(in: 1...1000))")
            }
        }
    }
}

이번엔 @StateObject로 선언하였습니다.

똑같이 문제를 해결할 수 있었습니다.

즉, 정리해 보자면,

외부에서 주입을 통해 ObservableObject 객체를 받는다면, @ObservedObject 사용을 권장 드리고,

내부에서 인스턴스화해서 사용한다면, 안전하게 @StateObject 사용을 권장 드립니다.

여기까지 @ObservedObject와 @StateObject에 대해 알아보았습니다.

원래 2편을 기획했는데 양 조절을 실패했네요.

다음 편에서 @Environment에 대해 알아보겠습니다.

 

저작자표시 (새창열림)

'iOS&Swift' 카테고리의 다른 글

[Swift] @Environment에 대해 알아보기(Property Wrapper 3편)  (0) 2024.01.20
[Swift] @State, @Binding, @Published 대해 알아보기(Property Wrapper 1편)  (0) 2024.01.19
[Swift] Alamofire의 RequestInterceptor을 사용해 토큰 갱신하기  (0) 2024.01.18
Combine 뽀개기 4장: Scheduler  (0) 2023.07.26
Combine 뽀개기 3장: Subject  (0) 2023.07.26
'iOS&Swift' 카테고리의 다른 글
  • [Swift] @Environment에 대해 알아보기(Property Wrapper 3편)
  • [Swift] @State, @Binding, @Published 대해 알아보기(Property Wrapper 1편)
  • [Swift] Alamofire의 RequestInterceptor을 사용해 토큰 갱신하기
  • Combine 뽀개기 4장: Scheduler
Esiwon
Esiwon
iOS 개발 블로그
  • Esiwon
    시원한 코드 기록
    Esiwon
  • 전체
    오늘
    어제
    • 분류 전체보기 (70)
      • iOS&Swift (24)
      • git & github (1)
      • 코테 (41)
      • 네부캠 회고 (4)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃허브
  • 공지사항

  • 인기 글

  • 태그

    코딩테스트
    백준
    회고
    노드
    BFS
    비동기
    완전탐색
    네부캠
    Race Condition
    동시성
    브루트포스 알고리즘
    실버
    구현
    ios
    dfs
    재귀
    그리디
    Swift
    GCD
    코테
    photos
    Property wrapper
    챌린지
    탐색
    다이나믹 프로그래밍
    알고리즘
    이분탐색
    Combine
    photoUI
    PhotoKit
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Esiwon
[Swift] @ObservedObject와 @StateObject의 차이에 대해 알아보기(Property Wrapper 2편)
상단으로

티스토리툴바