Post

Swift 연산프로퍼티, 프로퍼티 감시자

실제 값을 저장하는 프로퍼티가 아니라 특정 상태에 따른 값을 연산하는 프로퍼티

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 키워드와 같은 효과) 하지만 지역변수, 지역 상수는 절대 지연 연산되지 않는다

This post is licensed under CC BY 4.0 by the author.