Property [옵저버] - ehrldyd15/Swift_Skills GitHub Wiki

Property [옵저버]

프로퍼티옵저버

프로퍼티 값의 변화를 관찰하는 것으로, 저장프로퍼티에 추가할 수 있다.

새 값의 속성이 현재 값과 동일하더라도 속성 값이 설정되면 호출된다.

값이 저장되기 직전에 호출되는 willSet, 새 값이 저장된 직후에 호출되는 didSet이 있다.

1. willSet

willSet 옵저버를 구현하면 값이 저장되기 직전에 새로 저장될 값이 파라메터로 전달된다.

이때 파라메터 이름은 지정할 수 있지만, 파라메터 이름과 괄호를 따로 지정하지 않을 경우 newValue로 사용한다.

var name: String = "Unknown" {
    willSet(newName) {
        print("현재 이름 = \(name), 바뀔 이름 = \(newName)")
    }
}

willSet 옵저버는 위와같이 구현을 하고 newName이라는 파라메터 이름을 내가 지정 가능하다.

var name: String = "Unknown" {
    willSet { ✅
        print("현재 이름 = \(name), 바뀔 이름 = \(newValue)") ✅
    }
}

파라메터를 생략하게되면 newValue로 접근이 가능하다.

var name: String = "Unknown" {
    willSet(newName) {
        print("현재 이름 = \(name), 바뀔 이름 = \(newName)")
    }
}

print(name) ✅ // Unknown

값을 변경하지 않고 호출을 하면 디폴드 값이 출력된다.

var name: String = "Unknown" {
    willSet(newName) {
        print("현재 이름 = \(name), 바뀔 이름 = \(newName)")
    }
}

name = "ABC" ✅ // 현재 이름 = Unknown, 바뀔 이름 = ABC

2. didSet

didSet 옵저버를 구현하면 값이 저장된 직후에 이전 프로퍼티의 값이 파라메터로 전달된다.

이때 파라메터 이름은 지정할 수 있지만, 파라메터 이름과 괄호를 따로 지정하지 않을 경우 oldValue로 사용한다.

var name: String = "Unknown" {
   didSet(oldName) {
        print("현재 이름 = \(name), 바뀌기 전 이름 = \(oldName)")
    }
}

didSet 옵저버는 위와같이 구현을 하고 oldName이라는 파라메터 이름을 내가 지정 가능하다.

var name: String = "Unknown" {
   didSet { ✅
        print("현재 이름 = \(name), 바뀌기 전 이름 = \(oldValue)") ✅
    }
}

willSet과 마찬가지로 파라메터 부분을 지우면 oldValue로 접근할 수 있다.

var name: String = "Unknown" {
   didSet(oldName) {
        print("현재 이름 = \(name), 바뀌기 전 이름 = \(oldName)")
    }
}

print(name) ✅ // Unknown

값을 변경하지 않고 출력하면 디폴트 값이 출력된다.

var name: String = "Unknown" {
   didSet(oldName) {
        print("현재 이름 = \(name), 바뀌기 전 이름 = \(oldName)")
    }
}

name = "ABC"

print(name) ✅ // 현재 이름 = ABC, 바뀌기 전 이름 = Unknown

3. willSet과 didSet 동시구현

var name: String = "Unknown" {
    willSet {
        print("현재 이름 = \(name), 바뀔 이름 = \(newValue)")
    }
    didSet {
        print("현재 이름 = \(name), 바뀌기 전 이름 = \(oldValue)")
    }
}
 
name = "ABC"

✅ // 현재 이름 = Unknown, 바뀔 이름 = ABC
✅ // 현재 이름 = ABC, 바뀌기 전 이름 = Unknown

출력의 순서는 아래와 같다.

  1. willSet이 먼저 실행

  2. 저장프로퍼티의 name의 값이 변경

  3. didSet이 실행

연산프로퍼티도 프로퍼티옵저버 추가할 수 있다.

위에서 언급했듯이 프로퍼티옵저버는 저장프로퍼티에 한해서 추가할 수 있는데

연산프로퍼티도 프로퍼티옵저버를 추가할 수 있다.

(부모 클래스의 연산프로퍼티를 오버라이딩 할 경우 프로퍼티옵저버를 추가할 수 있다.)

class Human {
    var name = "Unknown"
    var alias: String {
        get {
            return name
        }
        set {
            name = newValue
        }
        willSet { }  ❌ // error! 'willSet' cannot be provided together with a getter
        didSet  { }  ❌ // error! 'didSet' cannot be provided together with a getter
    }
}

위 처럼 입력하면 에러가 발생한다.

왜냐하면 setter를 통해 값 변경을 할 수 있는데 굳이 프로퍼티 옵저버를 만들 이유가 없기 때문이다.

class Human {
    var name = "Unknown"
    var alias: String {
        get {
            return name
        }
        set {
            name = newValue
        }
    }
}


class ABC: Human {
    override var alias: String {
        willSet {
            print("현재 alias = \(alias), 바뀔 alias = \(newValue)")
        }
        didSet {
            print("현재 alias = \(alias), 바뀌기 전 alias = \(oldValue)")
        }
    }
}

let abc: ABC = .init() 
abc.alias = "ABC"

✅ // 현재 alias = Unknown, 바뀔 alias = ABC
✅ // 현재 alias = ABC, 바뀌기 전 alias = Unknown

참고자료

https://babbab2.tistory.com/121?category=828998