@Published, @Binding, @ObservedObject, @State 같은 애들이 Property Wrapper 프로퍼티 래퍼입니다.
Property Wrapper 를 사용하는 이유는 특정 기능을 동작하게 하는데 좀 더 간단하게 코드를 짤 수 있도록 도와주고 코드의 중복을 없애 간결하게 만들어주기 때문입니다.
아래 예제 코드로 Property Wrapper 를 사용하기 전과 후의 차이를 봐서 얼마나 간결해졌는지 확인할 수 있습니다. width 와 height 는 값을 가져올 때 width 와 height 값과 10을 비교해서 작은 값을 반환하는 코드인데 로직이 중복되는 것을 확인할 수 있습니다.
struct Rectangle {
private var _width: Int
private var _height: Int
init(width: Int, height: Int) {
self._width = width
self._height = height
}
var width: Int {
get { return min(_width, 10) }
set { _width = newValue }
}
var height: Int {
get { return min(_height, 10) }
set { _height = newValue }
}
}
하지만 중복되는 로직을 아래처럼 @propertyWrapper로 선언하여 코드를 간결하게 만들 수 있습니다. 만일 @propertyWrapper로 만들어주면 wrappedValue는 반드시 만들어줘야합니다.
@propertyWrapper struct LimitedValueByTen {
private var value: Int = 0
var wrappedValue: Int {
get { self.value }
set { value = min(10, newValue) }
}
}
다음은 @propertyWrapper 를 통해서 간결하게 만든 LimitedValueByTen 구조체를 이용하여 Rectangle 구조체를 다시 만듭니다.
struct Rectangle {
@LimitedValueByTen var width: Int
@LimitedValueByTen var height: Int
}
위에서 만든 Rectangle 구조체를 사용하여 인스턴스를 만들고 인스턴스의 저장 프로퍼티에 값을 넣어 만약 10보다 크면 10이 저장되고 아니면 설정한 값이 저장되게끔 동작합니다.
var rectangle = Rectangle()
rectangle.height = 12
rectangle.width = 8
print(rectangle.height, rectangle.width) // 10, 8
만약 LimitedValueByTen 에 프로퍼티 래퍼에 초기값을 설정하는 방법은 2가지가 있다. 첫 번째로는 @propertyWrapper 에 초기화 함수를 구현한 경우, 두 번째로는 @propertyWrapper 내부에 초기값을 지정하는 경우다.
다음 코드는 프로퍼티 래퍼에 초기값을 @propertyWrappper 에 초기화 함수를 구현하는 예제 코드다
@propertyWrapper struct LimitedValueByTen {
private var value: Int
var wrappedValue: Int {
get { self.value }
set { value = min(10, newValue) }
}
init(wrappedValue initialValue: Int) {
self.value = initialValue
}
}
struct Rectangle {
@LimitedValueByTen var width: Int
@LimitedValueByTen var height: Int
}
var rectangle = Rectangle(width: 12, height: 8)
rectangle.height = 12
rectangle.width = 8
print(rectangle.height, rectangle.width) // 12, 8
다음 코드는 프로퍼티 래퍼에 초기값을 @propertyWrapper 내부에 미리 지정하는 경우다.
@propertyWrapper struct LimitedValueByTen {
private var value: Int = 0
var wrappedValue: Int {
get { self.value }
set { value = min(10, newValue) }
}
}
struct Rectangle {
@LimitedValueByTen var width: Int
@LimitedValueByTen var height: Int
}
var rectangle = Rectangle()
print(rectangle.height, rectangle.width) // 0, 0
조금 더 나아가 UserDefaults 에서도 사용될 수 있습니다. 다음 UserManager 클래스를 보면 get과 set 부분이 중복되는 것을 확인할 수 있는데 @propertyWrapper 를 사용해서 코드를 간결하게 만들 수 있습니다.
class UserManager {
static var usesTouchID: Bool {
get { return UserDefaults.standard.bool(forKey: "usesTouchID") }
set { UserDefaults.standard.set(newValue, forKey: "usesTouchID") }
}
static var myEmail: String? {
get { return UserDefaults.standard.string(forKey: "myEmail") }
set { UserDefaults.standard.set(newValue, forKey: "myEmail") }
}
static var isLoggedIn: Bool {
get { return UserDefaults.standard.bool(forKey: "isLoggedIn") }
set { UserDefaults.standard.set(newValue, forKey: "isLoggedIn") }
}
}
get 부분은 UserDefaults.standard.object(forKey: _) 를 사용하여 명시된 Key로 설정된 값을 반환하며, set 부분은 UserDefaults.standard.set(newValue, forKey: self.key) 를 통해서 위에 코드들을 간결하게 만들었습니다. 만일 UserDefault를 통해서 가져온 value 값이 T 타입이 아니라면 defaultValue 타입으로 대체하는 코드입니다.
@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
var wrappedValue: T {
get { UserDefaults.standard.object(forKey: self.key) as? T ?? self.defaultValue }
set { UserDefaults.standard.set(newValue, forKey: Key) }
}
}
@propertyWrapper UserDefault로 개선된 UserManager 클래스 입니다.
class UserManager {
@UserDefault(key: "usesTouchID", defaultValue: false)
static var usesTouchID: Bool
@UserDefault(key: "myEmail", defaultValue: nil)
static var myEmail: String?
@UserDefault(key: "isLoggedIn", defaultValue: false)
static var isLoggedIn: Bool
}
'SwiftUI' 카테고리의 다른 글
Button (0) | 2023.11.11 |
---|---|
wrappedValue, projectedValue (0) | 2023.11.10 |
Property, Method (0) | 2023.11.10 |
Environment (0) | 2023.11.08 |
URL (0) | 2023.11.08 |