# Swift 프로퍼티 옵저버(Property Observer)에 대해 알아보기

jeongmuyamette·2025년 7월 26일

TIL

목록 보기
70/72
post-thumbnail

프로퍼티 옵저버는 프로퍼티 값의 변화를 감지하고 반응할 수 있게 해주는 Swift의 강력한 기능입니다. 값이 변경되기 전과 후에 특정 코드를 실행할 수 있어 매우 유용합니다.

🔍 기본 개념

두 가지 프로퍼티 옵저버

  1. willSet: 값이 저장되기 직전에 호출
  2. didSet: 값이 저장된 직후에 호출
class Temperature {
    var celsius: Double = 0.0 {
        willSet {
            print("온도가 \(celsius)°C에서 \(newValue)°C로 변경될 예정입니다.")
        }
        didSet {
            print("온도가 \(oldValue)°C에서 \(celsius)°C로 변경되었습니다.")
        }
    }
}

let temp = Temperature()
temp.celsius = 25.0
// 출력:
// 온도가 0.0°C에서 25.0°C로 변경될 예정입니다.
// 온도가 0.0°C에서 25.0°C로 변경되었습니다.

📝 상세 사용법

1. willSet - 값 변경 전

class Counter {
    var count: Int = 0 {
        willSet(newCount) {  // 매개변수명 직접 지정 가능
            print("카운트가 \(count)에서 \(newCount)로 변경될 예정")
            
            // 변경을 막을 수는 없지만, 준비 작업 가능
            if newCount < 0 {
                print("⚠️ 경고: 음수 값이 설정됩니다!")
            }
        }
    }
}

let counter = Counter()
counter.count = 5   // willSet 호출
counter.count = -1  // 경고 메시지 출력

2. didSet - 값 변경 후

class Player {
    var score: Int = 0 {
        didSet(previousScore) {  // 매개변수명 직접 지정 가능
            print("점수: \(previousScore)\(score)")
            
            // 값 변경 후 추가 로직 실행
            if score > previousScore {
                print("🎉 점수가 올랐습니다!")
            }
            
            // didSet에서 다시 값을 변경할 수도 있음
            if score < 0 {
                print("점수는 0보다 작을 수 없습니다.")
                score = 0  // 재귀 호출되지 않음
            }
        }
    }
}

let player = Player()
player.score = 100  // 점수 증가
player.score = -10  // 자동으로 0으로 수정됨

🛠 실용적인 예제들

1. UI 업데이트

class ProgressView {
    var progress: Double = 0.0 {
        willSet {
            // 유효성 검사
            if newValue < 0.0 || newValue > 1.0 {
                print("⚠️ 진행률은 0.0~1.0 사이여야 합니다.")
            }
        }
        didSet {
            // UI 업데이트
            updateProgressBar()
            
            // 완료 시 알림
            if progress >= 1.0 {
                print("✅ 작업이 완료되었습니다!")
                notifyCompletion()
            }
        }
    }
    
    private func updateProgressBar() {
        let percentage = Int(progress * 100)
        print("진행률: \(percentage)%")
    }
    
    private func notifyCompletion() {
        print("🔔 완료 알림 전송")
    }
}

let progressView = ProgressView()
progressView.progress = 0.3  // 진행률: 30%
progressView.progress = 0.8  // 진행률: 80%
progressView.progress = 1.0  // 진행률: 100%, 완료 알림

2. 데이터 검증 및 변환

struct User {
    var email: String = "" {
        willSet {
            print("이메일 변경 시도: \(newValue)")
        }
        didSet {
            // 자동 소문자 변환
            if email != email.lowercased() {
                email = email.lowercased()
            }
            
            // 유효성 검사
            validateEmail()
        }
    }
    
    private mutating func validateEmail() {
        if !email.contains("@") {
            print("⚠️ 유효하지 않은 이메일 형식입니다.")
        } else {
            print("✅ 이메일이 설정되었습니다: \(email)")
        }
    }
}

var user = User()
user.email = "JOHN@EXAMPLE.COM"  // 자동으로 소문자로 변환

3. 상태 관리

enum ConnectionState {
    case disconnected, connecting, connected, error
}

class NetworkManager {
    var connectionState: ConnectionState = .disconnected {
        willSet {
            print("연결 상태 변경: \(connectionState)\(newValue)")
        }
        didSet {
            handleStateChange(from: oldValue, to: connectionState)
        }
    }
    
    private func handleStateChange(from oldState: ConnectionState, to newState: ConnectionState) {
        switch newState {
        case .connecting:
            print("🔄 연결 중...")
        case .connected:
            print("✅ 연결 완료")
            startHeartbeat()
        case .disconnected:
            print("❌ 연결 해제")
            stopHeartbeat()
        case .error:
            print("🚨 연결 오류 발생")
            attemptReconnection()
        }
    }
    
    private func startHeartbeat() { print("💓 하트비트 시작") }
    private func stopHeartbeat() { print("💔 하트비트 중지") }
    private func attemptReconnection() { print("🔄 재연결 시도") }
}

let networkManager = NetworkManager()
networkManager.connectionState = .connecting
networkManager.connectionState = .connected
networkManager.connectionState = .error

⚠️ 주의사항 및 제한사항

1. 초기화 시에는 호출되지 않음

class Example {
    var value: Int = 10 {  // 초기값 설정 시에는 옵저버 호출 안됨
        didSet {
            print("값이 변경됨: \(value)")
        }
    }
}

let example = Example()  // didSet 호출되지 않음
example.value = 20       // didSet 호출됨

2. 계산 프로퍼티에는 사용 불가

class Rectangle {
    var width: Double = 0.0
    var height: Double = 0.0
    
    // ❌ 계산 프로퍼티에는 옵저버 사용 불가
    var area: Double {
        get { return width * height }
        // willSet, didSet 사용 불가
    }
}

3. didSet에서 같은 프로퍼티 수정 시 재귀 호출 안됨

class SafeCounter {
    var count: Int = 0 {
        didSet {
            if count < 0 {
                count = 0  // 이 라인은 didSet을 다시 호출하지 않음
            }
        }
    }
}

🎯 언제 사용하나요?

1. UI 업데이트가 필요한 경우

  • 모델 데이터 변경 시 자동으로 뷰 업데이트

2. 데이터 유효성 검사

  • 입력값 검증 및 자동 수정

3. 상태 변화 추적

  • 로깅, 디버깅, 상태 기반 로직 실행

4. 부수 효과(Side Effect) 처리

  • 데이터 변경 시 관련된 다른 작업들 자동 실행

🔄 다른 방법들과의 비교

프로퍼티 옵저버 vs 계산 프로퍼티

class DataManager {
    // 저장 프로퍼티 + 옵저버
    var rawData: String = "" {
        didSet {
            processedData = rawData.uppercased()
        }
    }
    
    // 계산 프로퍼티
    var processedData: String = ""
    
    // 또는 계산 프로퍼티로만 처리
    var alternativeProcessedData: String {
        return rawData.uppercased()
    }
}

📚 TIL 요약

  • 프로퍼티 옵저버: 프로퍼티 값 변경을 감지하고 반응하는 기능
  • willSet: 값 변경 직전 호출 (newValue 사용 가능)
  • didSet: 값 변경 직후 호출 (oldValue 사용 가능)
  • 용도: UI 업데이트, 데이터 검증, 상태 추적, 부수 효과 처리
  • 제한사항: 초기화 시 미호출, 계산 프로퍼티 사용 불가
  • 특징: didSet에서 같은 프로퍼티 수정 시 재귀 호출 안됨

프로퍼티 옵저버는 데이터의 변화를 자동으로 감지하고 적절히 반응할 수 있게 해주는 매우 유용한 기능입니다! 특히 MVVM 패턴이나 반응형 프로그래밍에서 핵심적인 역할을 합니다. 🚀

0개의 댓글