안녕하세요.
대충은 알지만 대충 알고 있던 @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
이번 글은 여기서 마무리.
'Swift' 카테고리의 다른 글
map, compactMap, flatMap (0) | 2024.08.18 |
---|---|
lazy var 클로저에서 retain cycle이 생기는 경우 (0) | 2024.06.20 |
protocol initializer가 클래스에서 required로 정의되어야 하는 이유 (0) | 2024.05.12 |
propertyWrapper로 UserDefaults 관리 (0) | 2024.05.11 |
@discardableResult (0) | 2024.01.28 |