XLOG

[SwiftUI] Observation (feat iOS 17, 써야하는 이유) 본문

Swift/SwiftUI

[SwiftUI] Observation (feat iOS 17, 써야하는 이유)

X_PROFIT 2024. 8. 17. 23:21

1. Observation 이란?

wwdc 23 에서 발표한 observation 은 프로퍼티의 변화를 추적하는 Swift 기능으로 같은 wwdc 23 에 발표한 swift 5.9에 신기능인 매크로를 사용하여 정의한 데이터 타입을 observable 하게 바꿔주는 기능이다.

2. 사용방법

정의한 class 에 @observable property wrapper 를 붙여준다.

import Foundation
import Observation

@Observable class DataModel {
	var count: Int = 0
}

이를 View 에서 사용할 땐 @State, @Environment, @Bindable, var 를 사할 수 있다. 여기서의 기준은

출처 : WWDC 23 (https://developer.apple.com/wwdc23/10149)

View의 부분일 경우 @State, 글로벌하게 변수를 사용할 경우 @Environment(Type.self) var, 단순히 바인딩이 필요할 경우(TextField 같이 값의 변화를 주기 위함) @Bindable 그게 아니라면 var 를 사용하면 된다.

 

3. 잘 사용하고 있는 ObservableObject 에서 Observable 을 사용해야 하는 이유

- 간단하게 정의한 데이터 타입자체에 Observable 매크로를 사용하여 ViewModel 처럼 활용이 가능하다.

- View 를 Re-render 하는 기준이 다르기 때문에 불필요한 Re-render를 최소화 할 수 있다.

기존 ObservedObject, StateObject, EnvironmentObject 의 경우 내부 프로퍼티 의 변화가 발생하면 해당 객체를 갖고 있는 모든 View는 변화를 감지하여 Re-render 가 이루어진다. 이게 State 와의 가장 큰 차이점이었다. @State 의 경우 변수를 mutate 시키더라도 이를 view 에서 직접적으로 사용하지 않는다면 view 를 re-render 를 시키지 않는다. @Observable 의 경우 사용할때 @State, @Environment 등을 사용하는 것 처럼 내부 프로퍼티의 변화가 있더라도 해당 객체의 변화하는 프로퍼티를 직접 view 에서 사용하지 않는다면 Re-render 를 발생시키지 않는다. 즉 불필요하 Re-render 를 최소화 할 수 있다.
import SwiftUI

final class OriginalObservable: ObservableObject {
    @Published var count: Int = 0
    
    func add() {
        self.count += 1
    }
    
    func minus() {
        self.count -= 1
    }
}

struct OriginalSubView1: View {
    
    @EnvironmentObject var vm: OriginalObservable
    
    var body: some View {
        VStack {
            let _ = Self._printChanges()
            Text("\(vm.count)")
            
            HStack {
                Button(action: { vm.minus() }) {
                    Text("-")
                }
                
                Button(action: { vm.add() }) {
                    Text("+")
                }
            }
        }
    }
}

struct OriginalSubView2: View {
    
    @EnvironmentObject var vm: OriginalObservable
    
    var body: some View {
        VStack {
            let _ = Self._printChanges()
            Text("Original Subview2")
        }
        
    }
}

struct ContentView: View {
    // 07:29
    var body: some View {
        VStack {
            OriginalSubView1()
            OriginalSubView2()
        }
    }
}

위에 코드를 보게 되면 OriginalSubView2 의 경우 OriginalSubView1 과 같은 EnvironmentObject 를 가지고 있다. 하지만 View에서 OriginalObservable 타입의 어떠한 프로퍼티도 View에서 사용하고 있지 않다.(극적인 예시를 위해.....불필요한 view model 을.....)

동작을 살펴보면

 

같은 EnvironmentObject를 가지고 있는 모든 View 는 ViewModel 의 변화를 감지하고 View를 Re-render를 시킨다.

그럼 새롭게 도입된 Observable을 확인해보자.

import SwiftUI
import Observation

@Observable final class NewObservable {
    var count: Int = 0
    
    func add() {
        self.count += 1
    }
    
    func minus() {
        self.count -= 1
    }
}

struct NewSubview1: View {
    
    @Environment(NewObservable.self) var vm: NewObservable
    
    var body: some View {
        VStack {
            let _ = Self._printChanges()
            Text("\(vm.count)")
            
            HStack {
                Button(action: { vm.minus() }) {
                    Text("-")
                }
                
                Button(action: { vm.add() }) {
                    Text("+")
                }
            }
        }
    }
}

struct NewSubview2: View {
    
    @Environment(NewObservable.self) var vm: NewObservable
    
    var body: some View {
        VStack {
            let _ = Self._printChanges()
            Text("New Subview2")
        }
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            NewSubview1()
            NewSubview2()
        }
    }
}

 

아까와 동일한 구조를 보이지만 New Subview2 의 경우 Re-render가 발생하지 않는 것을 확인할 수 있다.

- 추적할 프로퍼티에 @Published property wrapper 를 붙여줄 필요가 없다.(코드가 짧아진다)

등의 이유가 있다.

4. 느낌점

여기서 가장 흥미로운 점은 2번째이다. 몇일전 View Re-render 원리에 대한 공부를 했을 때 ViewModel 로 채택한 ObservableObject 는 하나의 프로퍼티의 변경으로 채택한 모든 View 의 변화를 만들어 냈고, 그렇기에 ViewModel 의 설계에 의한 데이터 흐름을 잘 설계해야한다는 것을 느꼈는데, iOS17 부터는 Observable 매크로를 통해서 이 부분을 조금 더 쉽게 효율적인 앱의 동작을 이끌어 낼 수 있다는 점이었다. 요즘 iOS 15, 16 을 최소버전으로 쓰고 있는 앱들이 많은 만큼 곧 iOS17 이 최소버전인 앱들도 생겨날 것이기에, 그 준비를 위해 Observable 을 사용하는 것을 고려해보는 것도 좋을 것 같다.