[Swift] 연산 프로퍼티(Computed Properties)와 프로퍼티 감시자(Property Observers)

silverCastle·2023년 1월 18일
0
post-thumbnail

연산 프로퍼티와 프로퍼티 감시자에 대해 예전에 언급한 적이 있지만 심도있게 다루지 않아 다시 정리하고자 한다.
[객체지향 프로그래밍과 swift] 프로퍼티(Property)

이번 게시글은 피자에 대한 예시로 설명할 것이다.
피자를 친구랑 같이 먹고 싶은데, 피자 인치에 따라서 피자 조각이 달라지게 하고 싶다고 가정하자.

💡 연산 프로퍼티(Computed Properties)

let pizzaInInches: Int = 10

var numberOfSlices: Int = 6

위 프로퍼티는 단순히 값을 저장하고 접근할 수 있는 저장 프로퍼티(Stored Properties)이다. pizzaInInches는 피자 인치, numberOfSlices는 피자 조각 수라고 생각하면 된다.

만약, 우리가 피자 조각 수를 다르게 하고 싶다면 어떻게 해야 할까?

numberOfSlices = 4

당연하게도 위와 같이 작성한다면 4조각으로 변경할 수 있다.
하지만, 피자를 14인치로 하고 피자 조각 수를 10조각으로 하고 싶다면? 일일이 코드를 작성할 것인가? 이것을 automatical하게 업데이트할 수는 없을까?
여기서 나온 개념이 연산 프로퍼티(Computed Properties)이다.

✍️ getter

우리의 목표는 14인치 피자와 그에 맞게 10조각으로 나누는 것이다.

let pizzaInInches: Int = 14

var numberOfSlices: Int {
    return pizzaInInches - 4
}

print(numberOfSlices)

결과

10

이 property에게 값을 돌려주기 위해 return를 작성한다. 이것은 마치 함수처럼 보인다..
연산 프로퍼티를 사용하기 위해서는 알아야할 점이 있다.

  • let이 아니라 var를 사용하자. 만약 상수인 let를 사용한다면 값을 바꿀 수 없고 연산 프로퍼티에 맞지 않는다!
  • 자료형을 명시해주자. 위와 같이 Int 타입이라고 명시하지 않고 추론을 한다면 값을 계산할 때 혼란을 줄 수 있다!

자 그럼 피자 인치를 바꿨을 때 automatical하게 바뀌는지 확인해보면?

let pizzaInInches: Int = 12

var numberOfSlices: Int {
    return pizzaInInches - 4
}

print(numberOfSlices)

결과

8

잘 바뀌는 걸 확인할 수 있다.

연산 프로퍼티는 우리가 많은 작업을 수행할 때 일어날 수 있는 잠재적 에러를 줄이는 데에 효과적이다.

func calcPizaaSlices() {
    numberOfSlices = pizzaInInches - 4
}

calcPizaaSlices()

물론 위와 같이 메서드를 생성한다면 같은 동작을 수행하겠지만..!
input도 없고 output도 없고, block 안에 있는 코드만 실행하는 거라면 굳이 더 많은 코드를 작성하지 않고 연산 프로퍼티를 쓰는 것이 적합하겠다.

우리가 작성한 것은 get을 명시하지만 않았지 short version의 getter라고 생각하면 된다.

var numberOfSlices: Int {
    get {
        return pizzaInInches - 4
    }
}

명시하려면 위와 같이 작성하면 된다.

✍️ setter

우리가 property의 값을 get할 때마다 즉, 가져올 때마다 실행되는 코드인 getter에 대해 배웠다.
이제는 property가 새로운 값으로 set될 때마다 즉, 설정될 때마다 실행되는 코드인 setter에 대해 알아보자.

let pizzaInInches: Int = 12

var numberOfSlices: Int {
    get {
        return pizzaInInches - 4
    }
    set {
        print("numberOfSlices의 새로운 값: \(newValue)")
    }
}

numberOfSlices = 12

결과

numberOfSlices의 새로운 값: 12

newValue는 우리가 따로 생성해주지 않아도 자동 생성된다. 새로운 값으로 업데이트될 때 그 즉시 set이 실행된다.

만약, 저기에서 setter를 없애버리면 어떻게 될까?

Cannot assign to value: 'numberOfSlices' is a get-only property

에러를 내뱉게 된다. why? 새로운 값에 대해 set을 할 수 없기 때문! 오직 get-only만 되는 것이다.

💡 프로퍼티 감시자(Property Observers)

getter를 사용해서 value를 계산하고 싶지 않고 단지 value가 변화할 때만 관찰하고 싶을 때 사용하는 것이 프로퍼티 감시자(Property Observers)이다.
property의 값이 바뀔 때 code를 트리거한다. 예를 들어, 피자 인치가 33인치라면 말도 안 되는 크기이기 때문에 최대 18인치를 넘어가게끔 하고 싶지 않다.
이때 우리는 프로퍼티 감시자를 사용하면 된다.

var pizzaInInches: Int = 10 {
    willSet {
    }
    didSet {
        if pizzaInInches >= 18 {
            print("pizzaInInches가 너무 큽니다.")
            pizzaInInches = 18
        }
    }
}

pizzaInInches = 33
print(pizzaInInches)

결과

pizzaInInches가 너무 큽니다.
18

observer 중 하나는 willSet이라고 부르고 다른 하나는 didSet이라고 부른다.
pizzaInInches의 값이 바뀌기 바로 직전에 willSet이 트리거되고 값이 바뀐다. 그 후 didSet이 실행된다.
순서: willSet 트리거 -> 값 변화 -> didSet 트리거
프로퍼티 감시자를 사용하기 위해서는 연산 프로퍼티와 마찬가지로 let 대신 var를 사용하자.

willSet에서는 newValue, didSet에서는 oldValue를 사용할 수 있다.

var pizzaInInches: Int = 10 {
    willSet {
        print("pizzaInInches: \(pizzaInInches)")
        print("새로운 값: \(newValue)")
    }
    didSet {
        print("pizzaInInches: \(pizzaInInches)")
        print("예전 값: \(oldValue)")
    }
}

pizzaInInches = 15

결과

pizzaInInches: 10	// 1
새로운 값: 15	// 2
pizzaInInches: 15	// 3
예전 값: 10	// 4

1에서는 실행 순서가 값 변화 이전에 willSet이 트리거되었기 때문에 여전히 10이다.
2에서는 newValue는 새로 바뀐 값이므로 15이다.
3에서는 값 변화 이후에 didSet이 트리거되었기 때문에 15이다.
4에서는 oldValue는 바뀌기 이전 값이므로 10이다.

0개의 댓글