iOS 공부하는 감자

Swift) Property Wrapper 본문

Swift

Swift) Property Wrapper

DongTaTo 2022. 7. 20. 00:00
반응형

Property Wrapper란

Swift 5.1에서 추가된 기능으로, 해석 그대로 프로퍼티를 한번 감싸는 것을 의미한다.

Property Wrapper를 사용하면 프로퍼티가 저장되는 방식을 관리하는 코드프로퍼티를 정의하는 코드 사이에 분리 계층을 추가하여 프로퍼티를 관리할 수 있다.

 

 

만약 특정 문자열 프로퍼티의 값을 대문자로만 사용하고 싶을 때, Property Wrapper를 사용하지 않고

연산 프로퍼티로 구현하면 아래와 같이 코드를 작성할 수 있다.

struct Person {
    private var _name: String
    
    // name에 값이 할당되면 _name에 그대로 저장하고
    // 값을 반환할 때는 _name에서 uppercased()메서드를 호출한 후 반환한다.
    var name: String {
        get { return self._name.uppercased() }
        set { self._name = newValue }
    }
    
    init(name: String) {
        self._name = name
    }
}


var somePerson = Person(name: "abcd")

print(somePerson.name)    // ABCD

somePerson.name = "qwqeweqwe"

print(somePerson.name)    // QWQEWEQWE

 

 

 

위 코드를 Property Wrapper를 사용하여 적용하면 프로퍼티 자체에 연결할 수 있어서

보일러플레이트 코드와 코드 재사용성을 높혀준다.

 

프로퍼티를 가질 수 있는 타입(class, struct, enum)에 @Property Wrapper 키워드를 붙여서 구현하며, 프로퍼티가 해야할 행동을 정의하는 타입으로 사용할 수 있게 한다.

 

여기서 @Property Wrapper 키워드는 직접 사용하려는 타입에 붙이는게 아니라 (위 코드에서는 Person타입)

프로퍼티가 해야 할 행동을 정의하는 타입에 붙여준다.

 

예시를 보면 이해가 쉽다..

 

1.  프로퍼티가 해야 할 행동을(대문자화) 정의하는 타입 생성

@propertyWrapper
struct Uppercase {
    private var value: String
    
    // wrappedValue는 필수 구현사항
    var wrappedValue: String {
        get { return self.value.uppercased() }
        set { self.value = newValue }
    }
    
    init(wrappedValue initialValue: String) {
        self.value = initialValue
    }
}

 

 

 

2. 실제 사용할 프로퍼티 앞에 "@Property Wrapper 타입 이름"을 붙여준다.

struct Person {
    @Uppercase
    var name: String
}

 

 

 

3. Property Wrapper를 적용시킨 프로퍼티를 사용해본다.

var somePerson = Person(name: "abcd")

print(somePerson.name)

somePerson.name = "qwer"

print(somePerson.name)

 

 

좀 정리하자면..

@Property Wrapper 키워드를 부착? 채택?한 타입에서 프로퍼티가 해야 할 행동을 정의해준다. (약간 확장 메서드처럼.. 생각해도 괜찮을 것 같기도 함..)

Property Wrapper를 붙여주면 wrappedValue라는 연산 프로퍼티를 내부에 필수로 구현해야 한다.

 

이렇게 프로퍼티가 해야 할 행동을 정의했다면, 이 행동을 수행할 프로퍼티에 해당 타입의 이름을 부착해준다.

부착만 잘(?) 해주면 별다른 추가 코드 없이, 직접 정의한 Property Wrapper부착 타입의 행동을 프로퍼티가 채택한다..

 

정리가 안된거같은디.. 머릿속에서는 이해가 좀 된거같은데 텍스트로 표현이 어렵다 ㅠ

 

 

 


 

 

UserDefault에 Property Wrapper 사용하기

WWDC 19에서도 Property Wrapper를 설명하기 위해 애플이 UserDefaults를 예시로 들었다고 한다.

뭔가 애플 공인 사용법(?) 이니까 잘 기억해두었다가 사용하면 좋을 것 같다.

 

위에서 작성한 Property Wrapper 사용과 동일한 순서로 진행한다.

 

1.  프로퍼티가 해야 할 행동을(대문자화) 정의하는 타입 생성

(프로퍼티가 해야 할 행동을 정의 -> 연산 프로퍼티로 UserDefaults 사용)

 

// 다양한 타입을 수용하기 위해 제네릭 문법 사용 <T>
@propertyWrapper
struct UserDefault<T> {
    let key: String
    let defaultValue: T   // key값에 대응하는 value가 없다면 반환해줄 기본 데이터
    
    var wrappedValue: T {
        // key값에 대응하는 value를 타입캐스팅 후 반환
        get { return UserDefaults.standard.object(forKey: self.key) as? T ?? defaultValue }
        
        //
        set { UserDefaults.standard.set(newValue, forKey: self.key) }
    }
}

 

 

2. 실제 사용할 프로퍼티 앞에 "@Property Wrapper 타입 이름"을 붙여준다.

 

 

(UserDefaults에 저장하고 꺼내올 데이터)

class UserDefaultManager {
    
    @UserDefault(key: "isSelected", defaultValue: false)
    static var isSelected: Bool
    
    
    @UserDefault(key: "name", defaultValue: "홍길동")
    static var name: String
    
    
    @UserDefault(key: "email", defaultValue: nil)
    static var email: String?
    
}

 

 

 

3. Property Wrapper를 적용시킨 프로퍼티를 사용해본다.

프로퍼티에 값을 할당하면, 자동으로 UserDefaults에 저장하고,

프로퍼티를 통해 값을 조회하면, UserDefaults에서 해당 프로퍼티가 가지고 있는 key값을 바탕으로 value를 조회하여 반환한다.

// 1.
print(UserDefaultManager.isSelected)     // false : 설정했던 기본값이 반환됨

UserDefaultManager.isSelected = true     // 자동으로 해당 프로퍼티가 가진 key값을 사용하여 UserDefaults에 내가 할당한 값 저장

print(UserDefaultManager.isSelected)     // true : UserDefaults에 잘 저장되었음을 확인



// 2.
print(UserDefaultManager.email)          // nil : 설정했던 기본값 반환

UserDefaultManager.email = "abc@ab.com"  // 자동으로 해당 프로퍼티가 가진 key값을 사용하여 UserDefaults에 내가 할당한 값 저장

print(UserDefaultManager.email)          // Optional("abc@ab.com") : 잘 저장되었음을 확인

 

 

 

 


 

 

☘️ SeSac 2주차 과제 코드 수정

기념일 계산기의 Date를 UserDefaults에 저장하는 방식에 propertyWrapper를 적용해 보았다.

 

1. 타입 생성

@propertyWrapper
struct UserDefault<T> {
    let key: String
    let defaultValue: T
    
    var wrappedValue: T {
        get { UserDefaults.standard.object(forKey: self.key) as? T ?? defaultValue }
        set { UserDefaults.standard.set(newValue, forKey: self.key) }
    }
}


class UserDefaultManager {
    
    static var shared = UserDefaultManager()
    
    private init() {}
    
    @UserDefault(key: "anniversaryDateKey", defaultValue: Date())
    var anniversaryDateKey: Date
}

 

 

2. 데이터를 저장하는 코드

// 기존
UserDefaults.standard.set(dateString, forKey: AnniversaryKey.anniversary.rawValue)

// 변경 후
UserDefaultManager.shared.anniversaryDateKey = datePicker.date

 

 

3. 데이터를 불러오는 코드

// 기존
if let dateString = UserDefaults.standard.string(forKey: AnniversaryKey.anniversary.rawValue), let date = stringToDate(dateString) {
	...
}

// 변경 후
let savedDate = UserDefaultManager.shared.anniversaryDateKey

 

반응형

'Swift' 카테고리의 다른 글

Swift) Choosing Between Structures and Classes  (0) 2023.01.30
Operation, OperationQueue (feat. GCD)  (0) 2022.12.28
split() vs components()  (0) 2022.06.04
문자열 다루기  (0) 2022.01.31