이전 포스팅

연산 프로퍼티

  • 연산 프로퍼티 (Computed Property)는 값을 제공한다는 점에서는 저장 프로퍼티와 같지만, 실제 값을 저장했다가 반환하는것이 아니라, 다른 프로퍼티의 값을 연산 처리하여 간접적으로 제공한다. 연산 프로퍼티에서는 내부적으로 get, set 구문을 사용한다. get 구문은 필수이며, set은 선택적이다.
	class 객체명 {
    	var 프로퍼티명 : 타입 {
        	get{
            	필요한 연산 과정
                return 반환값
            {
            set(매개변수명) {
            	필요한 연산 구문
            }
        }
    }
  • 연산 프로퍼티는 다른 프로퍼티에 의존적이거나, 혹은 특정 연산을 통해 얻을 수 있는 값을 정의할때 사용한다. 아래는 연산 프로퍼티의 예시이다.
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 + (self.sizedWidth / 2)
        }
        set(newCenterX) {
            originX = newCenterX - (self.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
  • 결국 연산 프로퍼티의 주된 목적은 변환되는 다른 프로퍼티의 따라 연산이 필요한 값을 얻기 위함이라고 생각하면 된다. 예시에서 보여준것 처럼 CenterX, CenterY는 다른 프로퍼티의 영향을 받기 때문에, 매번 값들이 변경 될때마다 직접 다시 계산하는것이 아니라, 변환되게 끔 설정하는 것이다.
  • 중요하지 않을수도 있지만, 만약에 연산 프로퍼티에서 다른 프로퍼티를 참조하여 연산을 한다고 가정을 하고, 다른 프로퍼티가 변경이 되었을때, 그 순간에 해당 연산 프로퍼티도 자동으로 값이 변환되는것이 아니라, 우리가 참조, 호출, 접근 하지 않는 이상 자동으로 변환되는것은 아니다.
  • 이해가 안되는 부분은, 다른 언어에서는 getter, setter가 따로 있는 반면에, swift에서는 연산 프로퍼티를 사용하는것인데, 그 이유가 정확히 무엇인지 궁금하다. 어떻게 생각해보면 getter,setter보다 아무래도 결국은 프로퍼티 (변수?)를 사용하는거니깐 조금더 직관적일수도 있다고 생각하지만...
  • 그리고 swift에서는 아래 코드처럼 오류가 난다.
	// 오류
	var temp : Double = self.origin.x + self.origin.y 	
    
    // 연산 프로퍼티를 사용해야 함. get{} 생략한 연산 프로퍼티
    var temp : Double {
        return self.origin.x + self.origin.y
    }

프로퍼티 옵저버

  • 프로퍼티 옵저버 (Property Observer)는 특정 프로퍼티를 계속 관찰하고, 해당 프로퍼티의 값이 변경되면 호출된다. 프로퍼티 옵저버는 두 종류로 분류 된다.

    willSet : 프로퍼티 값이 변경되기 직전에 호출되는 옵저버
    didSet : 프로퍼티 값이 변경된 직후에 호출되는 옵저버

struct Job {
    var oldValue : Int
    
    var income: Int = 0 {
        willSet(newIncome) {
            print("willSet가 호출되었습니다.")
            print("InCome = \(newIncome)")
        }
        
        
        didSet {
            print("didSet가 호출되었습니다.")
            print("InCome = \(income)")
            print("oldValue = \(oldValue)")
            print("self.oldValue = \(self.oldValue)")
        }
    }
}


var temp = Job(oldValue: 1000)
temp.income = 100

// 출력 결과
/*
willSet가 호출되었습니다.
InCome = 100
didSet가 호출되었습니다.
InCome = 100
oldValue = 0
self.oldValue = 1000
*/
  • 여기서 oldValue 와 self.oldValue의 차이는, 프로퍼티 옵저버에서 기본적으로 제공되는 oldValue 변수가 있다. 이 변수는 프로퍼티가 변경되기 전 값을 저장하고 있다. 즉 위 예시에서 oldValue는 income이 변경되기 전 값인 0 을 의미하는것이고, self.oldValue는 Job 클래스의 멤버변수를 뜻하는것이다. oldValue는 didSet에서만 사용가능하다.
  • 결국 willSet -> 프로퍼티 값 변경 -> didSet 이런 순서대로 실행 된다는것을 알 수 있다.
  • 지금 당장은 이걸 어디서 사용할지 몰라도, 예상하는건 값이 변경되기 전에 예외처리를 할 수도 있다. 단 프로퍼티이 변경될 값은 상수로 전달되기 때문에, 참조는 가능하지만 변경은 불가능하다. 즉 willset를 쓰더라도 property에 변경될 값은 절대 변경 불가능하다는것이다. 다만 fatalError나 return 뭐 이런식으로 멈출수는 있다.

타입 프로퍼티

  • 앞에서 공부한 저장 프로퍼티와 연산 프로퍼티는 인스턴스를 생성한 후에 인스턴스를 통해 참조할 수 있지만, 타입 프로퍼티는 인스턴스를 생성하지 않고 클래스나 구조체 자체에 저장되는 값이라고 생각하면된다. 다르게 말하면, 해당 클래스나 구조체로 정의된 인스턴스들 모두 영향을 받게 된다.
  • Static 키워드를 통해 타입 프로퍼티를 정의할 수 있다. 클래스에서는 class 키워드도 있는데, 해당 키워드는 연산 프로퍼티에만 붙일 수 있다. class 와 static 키워드의 차이는, 상속받은 하위 클래스에서 class 키워드를 사용한 타입 프로퍼티는 재정의를 할 수있다.
struct Foo {
    // 타입 저장 프로퍼티
    static var sFoo = "구조체 타입 프로퍼티값"
    
    // 타입 연산 프로퍼티
    static var cFoo: Int {
        return 1
    }
}

class Boo {
    // 타입 저장 프로퍼티
    static var sFoo = "클래스 타입 프로퍼티값"
    
    // 타입 연산 프로퍼티
    static var cFoo: Int {
        return 10
    }
    
    // 재정의가 가능한 타입 연산 프로퍼티
    // 클래스, 연산 프로퍼티에서만 사용 가능. 
    class var oFoo: Int {
        return 100
    }
}
  • 주의 해야 할점은, 타입 프로퍼티는 인스턴스를 통해 접근이 안된다.
var temp = Foo()
print(temp.sFoo) // 오류, 접근 불가
print(Foo.sFoo) // 정상 출력
  • class 키워드를 하위 클래스에서 재정의하는 예시 코드는 아래와 같다.
class SuperClass {
    class var typeProperty: String {
        return "SuperClass"
    }
    static var str : String = "This is a string from SuperClass" 
}

class SubClass: SuperClass {
    override class var typeProperty: String {
        return "SubClass"
    }
    
    /* 오류 
    override static var str : String = "This is a string from SubClass" 
    */
}
print(SuperClass.typeProperty) // "SuperClass"
print(SubClass.typeProperty) // "SubClass"
print(SuperClass.str) // "This is a string from SuperClass" 
print(SubClass.str) // "This is a string from SuperClass" 
  • override 키워드는 상속관계에서 사용되는 키워드로, 상위 클래스에서 정의된 메서드, 프로퍼티를 재정의할 때 사용하는 키워드다. 지금까지 배운 내용대로라면, 부모클래스에서 class 키워드를 통해 타입 프로퍼티를 정의 했을때만 자식 클래스에서 사용 가능한 키워드다.
profile
개발자 (진)

0개의 댓글