8.2.2 연산 프로퍼티

연산 프로퍼티(computed property)는 필요한 값을 제공한다는 점에서 저장 프로퍼티와 같지만, 실제 값을 저장했다가 반환하지는 않고 대신 다른 프로퍼티의 값을 연산 처리하여 간접적으로 값을 제공합니다. 프로퍼티의 값을 참조하기 위해 내부적으로 get구문을 사용하고, 선택적으로 set구문을 추가할 수 있습니다. set구문은 선택적이지만 get구문은 필수적입니다. 연산 프로퍼티는 항상 클래스나 구조체 또는 열거형 내부에서만 사용할 수 있습니다.

class/struct/enum 객체명 {
    ...
    var 프로퍼티명: 타입 {
        get {
            필요한 연산 과정
            return 반환값
        }
        set(매개변수명) {
            필요한 연산구문
        }
    }
}

연산 프로퍼티는 다른 프로퍼티에 의존적이거나, 혹은 특정 연산을 통해 얻을 수 있는 값을 정의할 때 사용합니다. 대표적으로 나이가 이에 속합니다. 나이는 출생 연도에 의존적이며, 현재 연도를 기준으로 계산해야 하기 때문입니다. 예제를 보겠습니다.

import Foundation

struct UserInfo {
    // 저장 프로퍼티 : 태어난 연도
    var birth: Int!
    
    // 연산 프로퍼티 : 올해가 몇년도인지 계산
    var thisYear: Int! {
        get {
            let df = DateFormatter()
            df.dateFormat = "yyyy"
            return Int(df.string(from: Date()))
        }
    }
    
    // 연산 프로퍼티 : 올해 - 태어난 연도 + 1
    var age: Int {
        get {
            return (self.thisYear - self.birth) + 1
        }
    }
}

let info = UserInfo(birth: 1980)
print(info.age)

// 실행 결과
37

이번에는 조금 더 복잡한 예제를 다루어 보겠습니다. 특정 사각형에 대한 정보를 저장하는 구조체에서 연산 프로퍼티를 사용하여 사각형의 중심 좌표를 구하는 예제입니다.

struct Rect {
    // 사작형이 위치한 기준 좌표(좌측 상단 기준)
    var originX: Double = 0.0, originY: Double = 0.0
    
    // 가로 세로 길이
    var sizeWidth: Double = 0.0, sizeHeight: Double = 0.0
    
    // 사각형의 X 좌표 중심
    var centerX: Double {
        get {
            return self.originX + (sizedWidth / 2)
        }
        set(newCenterX) {
            originX = newCenterX - (sizedWidth / 2)
        }
    }
    
    // 사각형의 Y 좌표 중심
    var centerY: Double {
        get {
            return self.originY + (self.sizeHeight / 2)
        }
        set(newCenterY) {
            self.originY = newCenterY - (self.sizeHeight / 2)
        }
    }
}

var square = Rect(originX: 0.0, originY: 0.0, sizeWidth: 10.0, sizeHeight: 10.0)
print("square.centerX = \(square.centerX), square.centerY = \(square.centerY)")

// 실행 결과
square.centerX = 5.0, square.centerY = 5.0

사각형의 기준 좌표 x,y와 가로세로 길이는 모두 저장 프로퍼티로 정의됩니다. 그런데 사각형의 중심 좌표를 저장 프로퍼티로 정의하기는 좀 곤란합니다.이 값은 도형의 기준 좌표 x,y와 가로세로 길이의 관계에서 얻어지는 의존적인 속성이기 때문입니다. 다시 말해 기준 좌표가 변경되거나 가로세로 길이가 변경되면 그에 따라 중심 좌표가 변경됩니다.

연산 프로퍼티를 사용하지 않고 프로퍼티 값 하나하나를 받아 직접 계산할 수도 있습니다. 하지만 매번 중심 좌표를 구해야 한다면 같은 코드가 계속 사용되어야 할 겁니다. 그 대신 연산 프로퍼티에 연산 구문을 정의해 놓으면 이 클래스를 사용하는 내낸 중심 좌표를 구하기 위해 반복적으로 코드를 작성해야 하는 일은 없어질 것입니다.

이번에는 두 개의 구조체를 정의하여 구조를 바꿔보겠습니다.

struct Position {
    var x: Double = 0.0
    var y: Double = 0.0
}

struct Size {
    var width: Double = 0.0
    var height: Double = 0.0
}     

좌표는 X와 Y값이 항상 함께 있어야 의미가 있고, 크기 역시 가로와 세로가 함께 있는 것이 좋습니다. 이 때문에 각각 묶어 구조체를 정의하였고, 사각형 Rect 구조체의 모습도 다음과 같이 변경됩니다

struct Rect {
    // 사각형이 위치한 기준 좌표(좌측 상단 기준)
    var origin = Position()
    // 가로 세로 길이
    var size = Size()
    // 사각형의 X 좌표 중심
    var center: Position {
        get {
            let centerX = self.origin.x + (self.size.width / 2)
            let centerY = self.origin.y + (self.size.height / 2)
            return Position(x: centerX, y: centerY)
        }
        set(newCenter) {
            self.origin.x = newCenter.x - (size.width / 2)
            self.origin.y = newCenter.y - (size.height / 2)        
        }
    }
}

let p = Position(x: 0.0, y: 0.0)
let s = Size(width: 10.0, height: 10.0)

var square = Rect(origin: p, size: s)
print("square.centerX = \(square.centerX), square.centerY = \(square.centerY)")

// 실행 결과
square.centerX = 5.0, square.centerY = 5.0

center 프로퍼티의 set 구문을 살펴봅시다. 우리가 연산 프로퍼티에 값을 할당하면 여기에 정의된 구문이 실행됩니다. 프로퍼티에 할당된 값은 set 다음에 오는 괄호의 인자값으로 전달되는데, 이때 인자값의 참조를 위해 매개변수가 사용됩니다. 앞의 예제를 본다면 newCenter가 매개변수의 이름인 것입니다. 만약 매개변수명이 생략된다면 newValue라는 기본 인자명이 사용됩니다.

매개변수만 있고 타입이 없는 이유는 타입이 이미 앞에서 정의되어 있기 때문입니다.

이번에는 중심 좌표의 값을 변경해 봅시다. 일반 프로퍼티에 값을 대입하는 것처럼 바꿀 중심 좌표를 적절한 타입으로 넣어주면 됩니다.

square.center = Position(x: 20.0, y: 20.0)
print("square.x \(square.origin.x), square.y = \(square.origin.y)")

center에 값을 할당하면 해당 인스턴스를 인자값으로 하는 set구문이 실행됩니다. origin 프로퍼티의 x, y 서브 프로퍼티값이 모두 바뀌었습니다.

square.x = 15.0, square.y = 15.0

앞에서 정의한 center에 set구문이 정의되어 있지 않으면 프로퍼티를 통해 값을 읽기만 할 뿐 할당은 불가능합니다. 이를 읽기 전용 프로퍼티라고 합니다.

var center: Position {
    get {
           let centerX = self.origin.x + (self.size.width / 2)
           let centerY = self.origin.y + (self.size.height / 2)
           return Position(x: centerX, y: centerY)
    }
}
// 위 center와 같음(get 블록 생략 가능)
var center: Position {
    let centerX = self.origin.x + (self.size.width / 2)
    let centerY = self.origin.y + (self.size.height / 2)
    return Position(x: centerX, y: centerY)
}
// 위와 기능 같음(함수로 표현)
func getCenter() -> Position {
    let centerX = self.origin.x + (self.size.width / 2)
    let centerY = self.origin.y + (self.size.height / 2)
    return Position(x: centerX, y: centerY)
}

읽기 전용 프로퍼티는 get블록만 작성하면 되는데, 사실 get블록을 생략하고 내용만 작성해도 됩니다. 또한 같은 기능을 매개변수가 없는 함수로도 표현 가능한데, 실무에서는 매개변수가 없는 함수 대신 읽기 전용 프로퍼티를 많이 쓰는 편입니다.

Last updated