@Property Wrapper
@Property Wrapper란?
Swift 5.1 이후 나온 Property Wrapper가 무엇인지 알아보고 예시 및 프로젝트에 사용한 UserDefault 코드를 리팩토링하는 방법까지 한번 알아보겠습니다.
https://github.com/apple/swift-evolution/blob/main/proposals/0258-property-wrappers.md
https://www.swift.org/blog/property-wrappers/
Exploring Swift: Property wrappers in the wild
Property wrappers were introduced in Swift 5.1 as a way to make it easier to reuse common programming patterns, but since then they have grown to work with local context, function and closure parameters, and more. We’re lucky enough to have lots of creat
www.swift.org
Documentation
docs.swift.org
Property Wrappers는 Swift5.1에서 도입되었고 일반적인 프로그래밍 패턴을 더 쉽게 재사용할 수 있는 방법이다.
공식문서에서 Property Wrappers를 위와 같이 소개하고 있습니다. 한마디로 반복되는 코드를 조금 더 효율적으로 사용될 수 있도록 하는 기능 정도로 정리하면 될 것 같습니다.
Property Wrappers를 정확히 몰랐던 분들도 이미 관습적으로 사용하고 있었을 가능성이 있습니다.
코드앞에 이상한 @ 가 붙어 있는 것을 보셨을텐데 그 코드가 바로 Property Wrappers입니다.
@State private var isPlaying: Bool = false
@available(iOS 15.0, *)
이런 코드들이 이미 property wrapper 였던 겁니다.
Property Wrapper를 언제 사용해야할까?
그럼 Preperty Wrapper가 언제 필요하고 언제 사용되어야할까?
다음 예시를 한번 살펴보겠습니다.
struct AStruct {
private var value: Int = 0
var multipleValue: Int {
get { self.value * 100 }
set { self.value = newValue }
}
init(value: Int) {
self.value = value
}
}
struct BStruct {
private var value: Int = 0
var multipleValue: Int {
get { self.value * 100 }
set { self.value = newValue }
}
init(value: Int) {
self.value = value
}
}
만약 저장속성이 있는데 계산속성을 활용해 그 값의 100을 곱해서 저장하는 구조체가 있다고 가정해보겠습니다.
위 예시에서 클래스를 복수개 만들었는데 중복되는 패턴이 보입니다. 계산속성에서 value에 *100을 해서 get하는 코드가 중복되었고 이 때 Property Wrapper를 활용해 재사용해볼 수 있겠습니다.
Property Wrapper 만들기
@propertyWrapper
struct Multiple {
private var value:Int = 0
}
재사용할 코드를 구조체 타입으로 만들어 놓겠습니다. 위 코드만 작성하면 컴파일 에러가 발생합니다. 에러를 살펴보겠습니다.
Property wrapper type 'Multiple' does not contain a non-static property named 'wrappedValue'
wrappedValue라는 non-static 속성이 없다는 것이고 그 이름으로 프로퍼티를 만들어주면 되겠습니다.
@propertyWrapper
struct Multiple {
private var value: Int = 0
var wrappedValue: Int {
get { self.value }
set { self.value = newValue * 100 }
}
init(wrappedValue initValue: Int) {
self.wrappedValue = initValue
}
}
중복된 핵심 코드를 @propertyWrapper로 만들었습니다. Property Wrapper를 만들때는 먼저 @propertyWrapper를 선언해주어야 합니다.
이제 중복된 코드를 제거해볼 수 있겠습니다.
struct AStruct {
@Multiple var value:Int = 0
}
인스턴스를 생성 후 프로퍼티 value의 값을 한번 확인해보겠습니다.
let a = AStruct(value: 100)
a.value // 10,000
직접 구현한 AStruct의 계산속성에 있던 중복 코드 없이도 value 초기화 값인 100에 100을 곱한 값이 잘 나오는 것을 볼 수 있습니다.
중복되는 코드를 정말 깔끔하게 정리할 수 있게 되었습니다. 프로젝트에서도 Property Wrapper를 잘 활용한다면 중복 코드없이 깔끔하고 가독성 좋은 코드를 작성할 수 있을 것 같습니다. 👍
Property Wrapper를 UserDefault 코드에 적용해서 리팩토링 해보기
프로젝트에 적용된 UserDefault를 살펴보면 데이터를 저장하거나 가져올 때 "key"만 같고 나머지 코드는 전부 다 같습니다. 물론 UserDefault.standard를 상수처리하여 조금 더 중복 코드를 줄여볼 수 있지만 Property Wrapper 를 적용해 조금 더 깔끔하게 리팩토링 해보겠습니다.
happy = UserDefaults.standard.integer(forKey: "happy")
good = UserDefaults.standard.integer(forKey: "good")
nomal = UserDefaults.standard.integer(forKey: "nomal")
upset = UserDefaults.standard.integer(forKey: "upset")
depressed = UserDefaults.standard.integer(forKey: "depressed")
key만 다르고 저장된 데이터를 가져오는 로직이 완전히 같습니다.
UserDefauls.standard 상수처리하여 중복 줄이기
let userDefault = UserDefaults.standard
happy = userDefault.integer(forKey: "happy")
good = userDefault.integer(forKey: "good")
nomal = userDefault.integer(forKey: "nomal")
upset = userDefault.integer(forKey: "upset")
depressed = userDefault.integer(forKey: "depressed")
조금 더 중복코드를 없애서 깔끔해보이긴한다. 하지만 아직 마음에 들지 않네요 ㅎ Property Wrapper를 적용해보겠습니다.
Property Wrapper 적용해보기
https://github.com/apple/swift-evolution/blob/main/proposals/0258-property-wrappers.md#user-defaults
위 페이지를 살펴보면 친절하게 User defaults의 Property Wrapper 적용 예시가 나옵니다. 바로 활용해보겠습니다.
@propertyWrapper
struct UserDefaultsWrapper<T> {
let key: String
let defaultValue: T
var wrappedValue: T {
get {
UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
우선 UserDefault Property Wrapper를 만들고 구조체를 통해 조금 더 편리하게 활용할 수 있도록 해보겠습니다.
struct UserDefaultsManager {
@UserDefaultsWrapper(key: "happy", defaultValue: 0)
static var happy: Int
@UserDefaultsWrapper(key: "good", defaultValue: 0)
static var good: Int
@UserDefaultsWrapper(key: "nomal", defaultValue: 0)
static var nomal: Int
@UserDefaultsWrapper(key: "upset", defaultValue: 0)
static var upset: Int
@UserDefaultsWrapper(key: "depressed", defaultValue: 0)
static var depressed: Int
}
UserDefault 데이터 get & set
- set
UserDefaultsManager.happy = happy
UserDefaultsManager.good = good
UserDefaultsManager.nomal = nomal
UserDefaultsManager.upset = upset
UserDefaultsManager.depressed = depressed
- get
UserDefaultsManager.happy
UserDefaultsManager.good
UserDefaultsManager.nomal
UserDefaultsManager.upset
UserDefaultsManager.depressed
단순이 해당 key에 값을 대입하는 것만으로도 저장할 수 있으며 해당 키를 프로퍼티처럼 접근하므로써 값을 불러올 수 있게 되었다. 조금 더 깔끔하고 구조체를 통해 관리할 수 있어 가독성있는 코드가 된 것 같습니다.
마지막으로 enum을 활용해서 @propertyWrapper를 활용하는 방법을 알아보겠습니다.
Enum을 활용한 @propertyWrapper
@propertyWrapper
struct CustomDefaults<T> {
let key: String
let defaultValue: T
var wrappedValue: T {
get {
UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.setValue(newValue, forKey: key)
}
}
}
enum UserDefaultsManager {
enum Key: String {
case isLoggedIn
case userID
}
@CustomDefaults(key: Key.isLoggedIn.rawValue, defaultValue: false) static var isLoggedIn
@CustomDefaults(key: Key.userID.rawValue, defaultValue: "") static var userID
}
'iOS > Swift' 카테고리의 다른 글
Swift - Swift에서는 왜 문자열을 Subscript로 접근할 수 없을까? (1) | 2023.12.12 |
---|---|
Swift - Method Swizzling (1) | 2023.12.01 |
ARC(Automatic Reference Counting) (0) | 2023.09.04 |
Swift - Protocol (0) | 2023.08.07 |
Swift - Type Casting (0) | 2023.07.29 |