Swift 연산프로퍼티, 프로퍼티 감시자
연산 프로퍼티
실제 값을 저장하는 프로퍼티가 아니라 특정 상태에 따른 값을 연산하는 프로퍼티
인스턴스 내/외부의 값을 연산하여 적절한 값을 돌려주는 접근자의 역할
은닉화된 내부의 프로퍼티 값을 간접적으로 설정하는 설정자의 역할을 할 수 있다
연산후 저장할 변수가 꼭 있어야한다 연산 프로퍼티는 값이 고정적이지 않으므로 변수로 선언
메서드를 대신 연산 프로퍼티를 사용하는 이유
인스턴스 외부에서 메서드를 통해 인스턴스 내부 값에 접근하려면 메서드를 두 개(접근자, 설정자)를 구현해야 한다 이 때문에 코드의 가독성이 떨어질 수 있다
연산 프로퍼티가 더 간편하고 직관적이다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//메서드로 구현된 접근자와 설정자
struct CoordinatePoint {
var x: Int
var y: Int
func oppositePoint() -> Self { //메서드 접근자 역할
return CoordinatePoint(x: -x, y: -y) //대칭점을 구해서 반환
}
//메서드 설정자 역할
mutating func set0ppositePoint(_ opposite: CoordinatePoint) {
x = -opposite.x
y = -opposite.y //점을 넣으면 대칭인 점이 위치가 된다
}
}
var myPosition: CoordinatePoint = CoordinatePoint(x: 10,y: 20)
print(myPosition) // CoordinatePoint(x: 10, y: 20) 현재 위치
print(myPosition.oppositePoint()) // CoordinatePoint(x: -10, y: -20) 대칭점
myPosition.set0ppositePoint(CoordinatePoint(x: 15,y: 21))//입력값과 대칭인 점이 위치
print(myPosition)//CoordinatePoint(x: -15, y: -21)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//연산 프로퍼티 사용
struct CoordinatePoint {
var x: Int
var y: Int
var oppositePoint: CoordinatePoint { // 연산 프로퍼티
get { //접근자
return CoordinatePoint(x: -x,y: -y)
}
set(opposite) { //설정자
x = -opposite.x
y = -opposite.y
}
}
}
var myPosition: CoordinatePoint = CoordinatePoint(x:10, y: 23)
print(myPosition)//CoordinatePoint(x: 10, y: 23) 현재 좌표
print(myPosition.oppositePoint)//CoordinatePoint(x: -10, y: -23) 대칭 좌표
myPosition.oppositePoint = CoordinatePoint(x: 12, y: 31) //대칭인 점을 넣음
print(myPosition) //CoordinatePoint(x: -12, y: -31) 현재 좌표
위 set을보면 set(opposite)로 선언되어 있는데 그냥 set으로 해도 사용가능하다 하지만 opposite.x가 아니라 newValue.x 이렇게 newValue를 이용해야 한다
연산 프로퍼티 이해
연산 프로퍼티를 처음 접했을 때 get은 뭐고 set은 뭔지 이해가 되지 않을 것이다 간단하게 연산 프로퍼티의 값을 읽으려고 하면 set이 실행되고 연산 프로퍼티의 값을 지정하려고 하면 set이 실행된다
이 때문에 연산 프로퍼티는 접근자의 역할(getter) 인스턴스 내/외부의 값을 연산하여 적절한 값을 돌려주 것과 은닉화된 내부의 프로퍼티 값을 간접적으로 설정하는 역할도 할 수 있다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct Point {
var x: Int
var y: Int
var oppositePoint: Point {
get {
return Point(x: -x, y: -y)
}
set {
x = -newValue.x
y = -newValue.y
}
}
}
var min: Point = Point(x:23,y:31)
print(min) //Point(x: 23, y: 31)
// 연산 프로퍼티값 읽기로 get부분이 실행된다
print(min.oppositePoint) //Point(x: -23, y: -31)
// 연산 프로퍼티 값지정으로 set부분 실행
min.oppositePoint=Point(x: 11,y: 21)
print(min)//Point(x: -11, y: -21)
위 코드를 보면 변수로 선언된 oppositePoint(연산 프로퍼티)에는 값이 저장되지 않았다 -> 연산만 실행되고 값은 모두 x, y,에 저장되었다 이 둘은 저장 프로퍼티 따라서 연산 프로퍼티는 값을 저장하지 않는다는 것을 확인할 수 있다
읽기 전용으로 연산프로퍼티를 만들 수 있다 이 경우는 get만 사용하여 구현하면 읽기 전용이 된다 get만 사용하여 구현하는것은 불가능하다
프로퍼티 감시자
프로퍼티 감시자란 프로퍼티 값이 변경될 때마다 적절한 작업을 취할 수 있는 역할을 한다
프로퍼티 값이 새로 할당될 때마다 프로퍼티 감시자는 호출된다(변경 값이 같아도)
저장 프로퍼티뿐만 아니라 프로퍼티를 재정의해 상속받은 저장 프로퍼티 또는 연산 프로퍼티에 적용 가능
프로퍼티 감시자에는 프로퍼티 값 변경 직전에 호출되는 willSet메서드와 변경 직후 호출되는 didSet메서드가 있다
willSet에 전달되는 전달인자는 변경될 값이고 didSet에 전달되는 전달인자는 변경 전 값이다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Account {
var money: Int = 0 {
willSet {
print("잔액이 \(money)에서 \(newValue)로 변경 됩니다")
}
didSet {
print("잔액이 \(oldValue)에서 \(money)로 변경 됐습니다")
}
}
}
var myMoney: Account = Account() //처음 이니셜라이저 때는 감시자호출안됨
//잔액이 0에서 1000로 변경 됩니다
myMoney.money = 1000
//잔액이 0에서 1000로 변경 됐습니다
상속받은 연산 프로퍼티의 프로퍼티 감시자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Account {
var money: Int = 0 {
willSet {
print("잔액이 \(money)원에서 \(newValue)원로 변경 됩니다 처음 클래스 시작")
} //2 money의 프로퍼티 감시자는 set에서 money값이 바뀌면서 실행
didSet {
print("잔액이 \(oldValue)원에서 \(money)원로 변경 됐습니다 처음클래스 끝")
} //3
}
var dollarValue: Double {
get {
print("이건 따로 추가 원을 달러로 바꾸는중 읽음 접근자")
return Double(money) / 1000.0
}
set {
money = Int(newValue * 1000)
print("잔액을 \(newValue)로 달러로 변경중입니다 설정자")
} //4
}
}
class ForeignAccount: Account {
override var dollarValue: Double {
willSet {
print("잔액이 \(dollarValue)달러에서 \(newValue)달러로 변경 됩니다 두번째 클래스 처음") //new는 바뀔 값
} // 1
didSet {
print("잔액이 \(oldValue)달러에서 \(dollarValue)달러로 변경 됐습니다 두번째 클래스 끝") //old는 이전 값
} // 5
}
}
var myMoney: ForeignAccount = ForeignAccount()
myMoney.dollarValue = 2
//이건 따로 추가 원을 달러로 바꾸는중 읽음 접근자
//이건 따로 추가 원을 달러로 바꾸는중 읽음 접근자
//잔액이 0.0달러에서 2.0달러로 변경 됩니다 두번째 클래스 처음 1
//잔액이 0원에서 2000원로 변경 됩니다 처음 클래스 시작 2
//잔액이 0원에서 2000원로 변경 됐습니다 처음클래스 끝 3
//잔액을 2.0로 달러로 변경중입니다 설정자 4
//이건 따로 추가 원을 달러로 바꾸는중 읽음 접근자
//잔액이 0.0달러에서 2.0달러로 변경 됐습니다 두번째 클래스 끝 5
접근자가 3번 실행되는데 그 이유를 모르겠음
연산 프로퍼티를 상속하고 오버라이딩을 해서 프로퍼티 감시자를 만들었다
자식 클래스의 프로퍼티 감시자 willSet이 먼저 실행되고 그다음 부모 클래스 내부 실행 후 자식 클래스로 빠져나온 다음 마무리된다
전역변수와 지역변수
앞에서 배운 연산 프로퍼티와 프로퍼티 감시자는 전역변수와 지역변수 모두에 사용할 수 있다 우리가 이제까지 변수라고 통칭했던 것을 전역변수 또는 지역변수는 저장 변수라고 할 수 있다 (저장 프로퍼티 역할) 전역변수나 지역 변수로 연산 변수를 구현할 수 있다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var wonInPocket: Int = 2000 { //저장 변수
willSet {
print("주머니에 돈이 \(wonInPocket)원에서 \(newValue)로 변경될 예정입니다")
} //1.2 money 값 변경 전
didSet {
print("주머니에 돈이 \(wonInPocket)원에서 \(oldValue)로 변경됨")
} // 2 money 값 변경 후
}
var dollarInPocket: Double { //연산변수
get {
return Double(wonInPocket) / 1000.0
}
set {
wonInPocket = Int(newValue * 1000.0) // 1.1 제일 먼저 실행 money의 값변경
print("주머니의 달러를 \(newValue)로 변경중 입니다") //3 마지막 출력
}
}
dollarInPocket = 3.5
//주머니에 돈이 2000원에서 3500로 변경될 예정입니다 1
//주머니에 돈이 3500원에서 2000로 변경됨 2
//주머니의 달러를 3.5로 변경중 입니다 3
전역변수 또는 전역 상수는 지연 저장 프로퍼티처럼 처음 접근할 때 최초의 연산이 이루어진다 (lazy 키워드와 같은 효과) 하지만 지역변수, 지역 상수는 절대 지연 연산되지 않는다