연산 프로퍼티와 프로퍼티 감시자에 대해 예전에 언급한 적이 있지만 심도있게 다루지 않아 다시 정리하고자 한다.
[객체지향 프로그래밍과 swift] 프로퍼티(Property)
이번 게시글은 피자에 대한 예시로 설명할 것이다.
피자를 친구랑 같이 먹고 싶은데, 피자 인치에 따라서 피자 조각이 달라지게 하고 싶다고 가정하자.
let pizzaInInches: Int = 10
var numberOfSlices: Int = 6
위 프로퍼티는 단순히 값을 저장하고 접근할 수 있는 저장 프로퍼티(Stored Properties)이다. pizzaInInches는 피자 인치
, numberOfSlices는 피자 조각 수
라고 생각하면 된다.
만약, 우리가 피자 조각 수를 다르게 하고 싶다면 어떻게 해야 할까?
numberOfSlices = 4
당연하게도 위와 같이 작성한다면 4조각으로 변경할 수 있다.
하지만, 피자를 14인치로 하고 피자 조각 수를 10조각으로 하고 싶다면? 일일이 코드를 작성할 것인가? 이것을 automatical하게 업데이트할 수는 없을까?
여기서 나온 개념이 연산 프로퍼티(Computed Properties)이다.
우리의 목표는 14인치 피자와 그에 맞게 10조각으로 나누는 것이다.
let pizzaInInches: Int = 14
var numberOfSlices: Int {
return pizzaInInches - 4
}
print(numberOfSlices)
결과
10
이 property에게 값을 돌려주기 위해 return를 작성한다. 이것은 마치 함수처럼 보인다..
연산 프로퍼티를 사용하기 위해서는 알아야할 점이 있다.
자 그럼 피자 인치를 바꿨을 때 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
}
}
명시하려면 위와 같이 작성하면 된다.
우리가 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만 되는 것이다.
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이다.