iOS/Swift

Swift] Property Observer

iOS_Dev_Ruel 2024. 8. 10. 13:36

안녕하세요 Ruel입니다.🎶

 

Property Observer에 대해서 아시나요?

Property Observer는 값의 변화를 감지하여 그에 따라 적절한 동작을 수행할 수 있게 해 줘요

이를 통해서 코드를 명확하게 작성할 수 있고, 유지보수가 쉬워진다고 생각해요

 

재밌는 Property Observer 한번 알아보도록 합시다.

 


Property Observer

앞서 간단하게 설명한 것처럼 Property Observer는 특정 프로퍼티의 값이 변경될 때 감지하고 이때 원하는 동작을 실행 시켜줘요.

음... 실생활에 예를 뭘로 들 수 있을까 고민해 봤는데...

누군가가 우리 집에 들어올 때마다 "누가 왔나??" 하면서 "확인하는 우리"라고 볼 수 있을까 해요

 

Property Observer는 같은 값으로 변경되더라도 동작해요

마치 집에 있던 누군가가 택배를 받으러 잠깐 나갔다가 들어와도 다시 확인한다고 할까??

 

❗️그러나 init() 메서드를 통해서 초기화할 때는 동작하지 않아요!

 

Property Observer에는 'willSet', 'didSet' 두 가지가 존재해요

  • willSet: 프로퍼티의 값이 변경되기 직전에 호출돼요. 새로운 값으로 바뀌기 전 "이제 곧 바뀌겠군!"이라고 알려주는 역할을 해요
    • 새로운 값은 'newValue'라는 기본 매개 변수로 제공돼요
  • didSet: 프로퍼티의 값이 변경된 직후 호출돼요. "이제 바뀌었군!" 하고 알려주는 역할이죠.
    • 이전 값은 'oldValue'라는 기본 매개 변수로 제공돼요
var propertyObserver: Int = 20 { 
    willSet {
        // newValue 제공
    }
    didSet {
        // oldValue 제공
    }
}

  

 

Property Observer는 프로퍼티의 값이 변해 View를 업데이트할 때 주로 사용된다고 생각해요.

예를 들면 점수가 바뀔 때마다 뷰를 업데이트하는??

var count: Int = 0 {
    didSet { 
        // 변경된 직후 호출되니까
        myPoint.text = "\(count)점 입니다."
    }
}

var count: Int = 0 {
    didSet(oldCount) { 
        // 변경된 직후 호출되니까
        myPoint.text = "이전 점수는 \(oldCount)점 입니다."
    }
}

var count: Int = 0 {
    didSet { 
        // 변경된 직후 호출되니까
        myPoint.text = "\(oldValue)점에서 \(count)점으로 변경되었습니다."
    }
}

 


온도계로 예로 들어볼게요

import Foundation

class Thermometer {
    var temperature: Double {
        willSet(newTemperature) {
            print("Temperature will change to \(newTemperature)°C")
        }
        didSet(oldTemperature) {
            print("Temperature did change from \(oldTemperature)°C to \(temperature)°C")
        }
    }
    
    init(temperature: Double) {
        self.temperature = temperature
    }
}

let thermometer = Thermometer(temperature: 25.0)
thermometer.temperature = 30.0
thermometer.temperature = 20.0

Property Observer

  • willSet(newTemperature): temperature의 값이 새로운 값으로 변경되기 직전 호출
    • newTemperature: 새로운 값
  • didSet(oldTemperature): temperature의 값이 새로운 값으로 변경된 후 호출
    • oldTemperature: 이전 값

실행 결과를 보면 온도계가 스스로 "곧 더워질 거야", "이제 좀 추워졌어"라고 하는 거 같아요.

Temperature will change to 30.0°C
Temperature did change from 25.0°C to 30.0°C
Temperature will change to 20.0°C
Temperature did change from 30.0°C to 20.0°C

 

Property Observer를 사용하면 값의 변화를 감지하고 때에 따라 적절한 동작을 실행시킬 수 있어요.


Property Observer를 안 쓰고는?

Property Observer를 안 쓰는 예로 한번 차이점 볼게요

 

1. getter와 setter 사용하기

 

import Foundation

class Thermometer {
    private var _temperature: Double
    var temperature: Double {
        get {
            return _temperature
        }
        set {
            if _temperature != newValue {
                print("Temperature will change to \(newValue)°C")
                let oldTemperature = _temperature
                _temperature = newValue
                print("Temperature did change from \(oldTemperature)°C to \(temperature)°C")
            }
        }
    }
    
    init(temperature: Double) {
        self._temperature = temperature
    }
}

let thermometer = Thermometer(temperature: 25.0)
thermometer.temperature = 30.0
thermometer.temperature = 20.0

위처럼 Property Observer를 사용하지 않고 getter와 setter를 정의하여 값이 변경될 때마다 필요한 동작을 수행해요.

앞서 Property Observer를 사용한 코드보다 복잡하지 않나요?? 

복잡하고 길고.... 이렇게 사용하다 보면 실수할 것 같네요

 

 

2. RxSwift 사용하기

import Foundation
import RxSwift

class Thermometer {
    private let disposeBag = DisposeBag()
    private let temperatureSubject = PublishSubject<Double>()
    
    var temperature: Double {
        didSet {
            temperatureSubject.onNext(temperature)
        }
    }
    
    init(temperature: Double) {
        self.temperature = temperature
        
        temperatureSubject
            .do(onNext: { newTemperature in
                print("Temperature will change to \(newTemperature)°C")
            })
            .subscribe(onNext: { [weak self] newTemperature in
                if let oldTemperature = self?.temperature {
                    print("Temperature did change from \(oldTemperature)°C to \(newTemperature)°C")
                }
            })
            .disposed(by: disposeBag)
    }
}

let thermometer = Thermometer(temperature: 25.0)
thermometer.temperature = 30.0
thermometer.temperature = 20.0
  • PublishSubject를 사용하여 temperature 값의 변화를 스트림으로 처리할 수 있어요.
  • PublishSubject를 사용하여 temperature 값의 변화를 감지하고 subscribe를 통해 새로운 값이 들어올 때마다 출력을 해요

 

3. Combine 사용해 보기

import Foundation
import Combine

class Thermometer {
    private var cancellables = Set<AnyCancellable>()
    private let temperatureSubject = CurrentValueSubject<Double, Never>(0.0)
    
    var temperature: Double {
        didSet {
            temperatureSubject.send(temperature)
        }
    }
    
    init(temperature: Double) {
        self.temperature = temperature
        
        temperatureSubject
            .handleEvents(receiveOutput: { newTemperature in
                print("Temperature will change to \(newTemperature)°C")
            })
            .sink(receiveValue: { [weak self] newTemperature in
                if let oldTemperature = self?.temperature {
                    print("Temperature did change from \(oldTemperature)°C to \(newTemperature)°C")
                }
            })
            .store(in: &cancellables)
    }
}

let thermometer = Thermometer(temperature: 25.0)
thermometer.temperature = 30.0
thermometer.temperature = 20.0

 

Combine에서는 CurrentValueSubject를 사용하여 temperature의 값의 변화를 스트림으로 처리해요

CurrentValueSubject를 사용하여 temperature값의 변화를 감지하고 sink를 통해 새로운 값이 설정될 때마다 출력을 처리하죠

그리고 handleEvents를 사용해서 sink에 값을 받기 전에 이벤트를 처리할 수 도 있어요

 

 

🤔 흠... RxSwift, Combine을 쓰면 현재 코드에서는 더 복잡한 거 같아요..... 

이런 방법들을 알고 상황에 맞게 사용해야겠죠~?

 

 


 

 

막무가내로 RxSwift와 Combine을 쓰다 보니 까먹는 상황이 생겼는데

이렇게 다시 보니 기억이 다시 떠오르네요

 

알고 있더라도 보고보고 또 봐야 할 거 같아요 

잊어버리지 않게 😭

 

 

잘못된 부분 혹은 새롭게 알려 주실 부분 있으시면 언제든지 댓글 남겨주세요.💬

감사합니다.🙇🏻‍♂️