Property Wrapper 

Property Wrapper프로퍼티가 저장되는 방식을 관리하는 코드프로퍼티가 정의되는 코드 사이에 분리된 계층을 추가해 준다.

 

즉, 도형의 각 변의 최대 길이를 정하고 싶다고 가정해 보자. 그렇다면 코드는 아래와 같이 구현해야 한다.

class Rectangular {
    private var _width: Int = 10
    private var _height: Int = 15
    private var maxLegth: Int = 30
    
    var width: Int {
        get { return _width }
        set { _width =  min(maxLegth, _width) }  // 저장하는 방식을 관리하는 코드
    }
    
    var height: Int {
        get { return _height }
        set { _height =  min(maxLegth, _height) }  // 저장하는 방식을 관리하는 코드
    }
}

 

여기서 중복되는 프로퍼티를 저장하는 방식에서 중복되는 코드가 발생하는데,

Property Wrapper를 사용하게 되면 프로퍼티를 저장하는 방식을 관리하는 부분을 한 번만 정의하여 재사용이 가능하다.

 

Property Wrapper를 정의하기 위해,

"wrappedValue" 프로퍼티를 정의한 structure, class, enumeration을 만들어야 한다. 

추가적으로 @propertyWrapper 라는 어노테이션을 작성해주어야 한다.

@propertyWrapper
struct MaxLength {
    private var maxValue: Int = 30
    private var length: Int = 10

    var wrappedValue: Int {
        get {
            return length
        }
        set {
            length = min(newValue, maxValue)
        }
    }
}

Property Wrapper를 적용할 프로퍼티 앞에 "@프로퍼티 이름"을 작성하면 된다.

class Rectangular {
    @MaxLength var width: Int
    @MaxLength var height: Int
}

let rectangular = Rectangular()

rectangular.width = 10
print(rectangular.width)   // 10

rectangular.height = 20
print(rectangular.height)  // 20

rectangular.width = 50
print(rectangular.width)   // 30

 

Property Wrapper는 structure, class, enumeration으로 정의하기 때문에, initializer 역시 정의가 가능하다.

@propertyWrapper
struct MaxLength {
    private var maxValue: Int
    private var length: Int

    var wrappedValue: Int {
        get {
            return length
        }
        set {
            length = min(newValue, maxValue)
        }
    }
    init() {
        wrappedValue = 20
        maxValue = 10
    }
    
    init(wrappedValue: Int) {
        self.wrappedValue = wrappedValue
        maxValue = 10
    }
    
    init(maxValue: Int) {
        self.maxValue = maxValue
        wrappedValue = 10
    }
    
    init(wrappedValue: Int, maxValue: Int) {
        self.wrappedValue = wrappedValue
        self.maxValue = maxValue
    }
}
class SomePolygon {
    
    //init()
    @MaxLength var side1: Int

    // init(wrappedValue: 1)
    @MaxLength var side2: Int = 1

    //init(wrappedValue: 10, maxValue: 20)
    @MaxLength(wrappedValue: 10, maxValue: 20) var side3: Int

    //init(wrappedValue: 20, maxValue: 10)
    @MaxLength(maxValue: 10) var side4: Int = 20
}

 

추가적으로 외부에서 접근할 수 있는 또 다른 projectedValue를 제공한다. 

다음 예시에서 projectedValue는 maxValue를 넘을 경우 false를 아닌 경우는 true를 반환한다.

@propertyWrapper
struct MaxLength {
    private var maxValue: Int = 30
    private var length: Int = 10

    //set의 경우 private이지만, get은 internal로 처리
    private(set) var projectedValue: Bool
    
    var wrappedValue: Int {
        get {
            return length
        }
        set {
            if(newValue > maxValue) {
                projectedValue = false
                length = maxValue
            }
            else {
                projectedValue = true
                length = newValue
            }
        }
    }
    
    init() {
        projectedValue = false
    }
}

projectedValue에 접근하기 위해선, 앞에 $를 붙이면 된다.

let rectangular = Rectangular()

rectangular.width = 10
print(rectangular.width)   // 10
print(rectangular.$width)  // true


rectangular.height = 20
print(rectangular.height)  // 20
print(rectangular.$height) // true


rectangular.width = 50
print(rectangular.width)   // 30
print(rectangular.$width)  // false

추가적으로 해당 경우에 projectedValue초기값을 주는 코드를 작성하고, initializer를 생략하면

 compile Error가 나게 된다. 

private(set) var projectedValue: Bool = false

이는 Structure는 initializer를 작성하지 않으면 Memberwise initializer를 제공한다. 

따라서, 외부에서 해당 프로퍼티를 set할 수 있게 되는데, 

private(set) 키워드에 의해 set은 private하게 설정해 놨기 때문에 compile Errorr가 나게 된다.

 

 

Local Variables(지역 변수) vs. Global Variables(전역 변수)

전역 변수메서드, 클로저, 클래스, 구조체, 열거형 등의 범위 밖에 정의된 변수를 의미하고, 

지역 변수메서드, 클로저, 클래스, 구조체, 열거형 등의 범위 안에 정의된 변수를 의미한다.

지역 변수 중 클래스, 구조체, 열거형과 연관 있는 값이면 프로퍼티가 된다. 

 

앞서 보았던, Stored Property, Computed Property, Property Observer는 모두 전역변수 혹은 지역변수로 사용이 가능하다.

 

Stored Property가 지역 변수 혹은 전역 변수로 사용하는 경우 저장 변수라고 한다. 상수로 쓰이는 경우는 저장 상수가 된다.

Computed Property 역시 지역 연산 변수, 전역 연산 변수로써 사용이 가능하나, Property 특성과 마찬가지로 상수는 불가능하다.

또한, Property Wrapper는 지역 저장 변수(Local Stored Variables)에만 적용이 가능하다. 

 

전역 변수(Global Variables), 전역 상수(Global Constants)는 항상 Lazy Stored Property처럼 처음 접근할 때 최초로 연산이 일어나게 된다. 따라서 lazy키워드를 쓸 필요가 없다. 

반면, 지역 변수(Local Variables), 지역 상수(Local Constants)에는 지연 연산이 적용되지 않는다.

 

 

Type Properties

지금까지 알아본 프로퍼티들은 타입을 정의하고 해당 타입의 인스턴스를 생성하였을 때

사용이 가능한 인스턴스 프로퍼티이다.

Type Property(타입 프로퍼티) 란, 인스턴스에 속하는 것이 아닌 타입 자체에 속하는 프로퍼티를 의미한다.

즉, 몇 개의 인스턴스를 생성하더라도 타입 프로퍼티의 값은 하나이며, C언어의 static과 유사하다.

 

Stored Type Property와 Computed Type Property가 있는데, 

Stored Type Property 같은 경우는 변수 혹은 상수로 선언이 가능한 반면, 

Computed Type Property는 변수로만 선언이 가능하다. 

 

또한, Stored Type Property는 지연 연산이 이루어지기 때문에 반드시 초깃값을 설정해야 한다. 

Lazy Stored Property과는 다르게 멀티 쓰레딩 환경에서도 한 번만 초기화된다는 것을 보장한다. 

 

다음과 같이 static키워드를 붙여주면 된다.

struct SomeStruct {
    //초깃값 좋야함.
    static var typeProperty: Int = 0
}

let structInstance = SomeStruct()

// 인스턴스가 아닌 타입을 통해 접근
print(SomeStruct.typeProperty)

위와 같이 Type Property는 인스턴스가 아닌 타입을 통해 접근해야 하며

인스턴스 생성과 상관없이 사용이 가능하다.

 

struct 정의 부분에서 접근할 때도 타입을 통해 접근하여야 한다. 

struct SomeStruct {
    //초깃값 좋야함.
    static var typeProperty: Int = 0
    
    func multiple2() -> Int {
        return SomeStruct.typeProperty * 2
    }
}

 

추가적으로 class의 경우에는 static 키워드 외에 추가로 class 키워드를 사용할 수 있는데, 

class 키워드는 sub-class에서 override를 허용한다.

 

 

Reference

Properties

복사했습니다!