Swift

@propertywrapper

Phililip 2024. 6. 7.
728x90

안녕하세요.

 

대충은 알지만 대충 알고 있던 @propertywrapper에 대해서 알아볼게요.

 


# @propertywrapper란?

propertywrapper는 프로퍼티가 저장되는 방식을 관리하는 코드와 프로퍼티를 정의하는 코드를 분리하는 역할을 합니다.

 

예를 들어, 프로퍼티에 값을 저장하기 전에 thread-safety 검사를 해야 하는 상황이 있다고 가정해 볼게요. 이 경우, 모든 프로퍼티에 대해 thread-safety 검사를 하는 코드를 작성해야 합니다.

 

하지만, propertywrapper를 사용하면 관리 코드를 포함한 propertywrapper를 정의해 두고, 여러 프로퍼티에 해당 propertywrapper를 적용하여 관리 코드를 재사용할 수 있어요.

 

# propertywrapper 정의 및 사용법

propertywrapper를 정의하려면, wrappedvalue 프로퍼티를 정의한 구조체, enum, 클래스를 만들어야 해요.

 

아래 코드에서 TwelveOrLess 구조체는 wrappedvalue가 항상 12 이하의 숫자를 갖도록 합니다.

 

@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
[주의] wrapperValue를 제외한 대부분의 property는 private으로 선언할 것!

propertywrapper 내부에서만 접근할 수 있도록 보장하기 위함이에요.

 

 

propertywrapper를 사용하려면 프로퍼티 앞에 propertywrapper를 attribute(속성)으로 작성합니다.

 

struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}
var rectangle = SmallRectangle()
print(rectangle.height) // 0
rectangle.height = 24
print(rectangle.height) // 12
[참고] propertywrapper는 local stored 변수에 적용할 수 있고, 전역 변수랑 computed 변수에는 사용할 수 없습니다.

 

# propertywrapper의 초기값 설정

init 생성자를 추가해서 propertywrapper의 초기값을 설정할 수 있어요.

 

@propertyWrapper
struct SmallNumber {
private var maximum: Int
private var number: Int
var wrappedValue: Int {
get { return number }
set { number = min(newValue, maximum) }
}
init() {
maximum = 12
number = 0
}
init(wrappedValue: Int) {
maximum = 12
number = min(wrappedValue, maximum)
}
init(wrappedValue: Int, maximum: Int) {
self.maximum = maximum
number = min(wrappedValue, maximum)
}
}

 

아래처럼 그냥 propertywrapper 속성만 적용된 프로퍼티는 init() 생성자가 호출됩니다.

 

struct ZeroRectangle {
@SmallNumber var height: Int // init() 생성자 호출
@SmallNumber var width: Int
}
var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// Prints "0 0"

 

아래처럼 초기값을 넣어준다면 init(wrappedValue:) 생성자가 호출됩니다.

 

struct UnitRectangle {
@SmallNumber var height: Int = 1 // init(wrappedValue:) 생성자 호출
@SmallNumber var width: Int = 1
}
var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// Prints "1 1"

 

이렇게 하면 init(wrappedValue:maximum:) 생성자가 호출되구요.

 

struct NarrowRectangle {
@SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
@SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
}
var narrowRectangle = NarrowRectangle()
print(narrowRectangle.height, narrowRectangle.width)
// Prints "2 3"
narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// Prints "5 4"

 

요렇게도 쓸 수 있습니다. height는 init(wrappedValue:) 생성자가 호출되고 width는 init(wrappedValue:maximum:) 생성자가 호출되겠죠?

 

struct MixedRectangle {
@SmallNumber var height: Int = 1
@SmallNumber(maximum: 9) var width: Int = 2
}
var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// Prints "1"
mixedRectangle.height = 20
print(mixedRectangle.height)
// Prints "12"

 

# projectedValue

propertywrapper는 래핑 된 값 외에도 projectedValue라는 것을 추가로 정의할 수 있어요.

 

projectedValue의 이름은 wrappedValue와 동일하지만 앞에 $를 붙여줘야 하고, 어떤 타입의 값이든 반환할 수 있습니다.

(다른 인스턴스를 반환하거나 self를 반환할 수도 있습니다.)

 

아래는 projectedValue가 정의된 propertywrapper 예시입니다.

 

@propertyWrapper
struct SmallNumber {
private var number: Int
private(set) var projectedValue: Bool // projectedValue 정의
var wrappedValue: Int {
get { return number }
set {
if newValue > 12 {
number = 12
projectedValue = true
} else {
number = newValue
projectedValue = false
}
}
}
init() {
self.number = 0
self.projectedValue = false
}
}
struct SomeStructure {
@SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()
someStructure.someNumber = 4
print(someStructure.$someNumber) // 접근할 때 앞에 '$'를 붙여줌.
// Prints "false"
someStructure.someNumber = 55
print(someStructure.$someNumber)
// Prints "true"

 

projectedValue는 언제 쓰면 좋을지 고민해 봐야겠어요ㅠ

 

# 활용

UserDefaults에 접근하는 propertywrapper를 정의하는 것이 대표적인 활용 방법입니다.

 

아래를 참고해 주세요!

 

propertyWrapper로 UserDefaults 관리

안녕하세요. propertyWrapper를 사용하면 UserDefaults 데이터 읽기/쓰기를 쉽게 하는 방법이 있어서 공유드리려구 해요. 프로젝트에 UserDefaultsWrapper라는 propertyWrapper를 추가해주세요. (당연히 이름은

phillip5094.tistory.com

 

# 참고

 

Documentation

 

docs.swift.org

 


이번 글은 여기서 마무리.

 

 

 

반응형