Tunko Development Diary

[Swift 5] @propertyWrapper 란? 본문

Development/iOS 개발

[Swift 5] @propertyWrapper 란?

Tunko 2022. 9. 26. 23:54

Property Wrappers는 Swift5 에서 사용할 수 있는 기능중 하나입니다.

코드를 줄이고 보기 좋게 만드는데 매우 유용합니다.

propertyWrapper란?

Swift 문서에 따르면 속성 래퍼는 속성이

저장되는 방식을 관리하는 코드와

속성을 정의하는 코드 사이에

분리 계층을 추가 합니다.

이렇게만 쓰여있으면 사실 이해하기 힘듭니다.

그래서 UserDefaults 를 예시로 들어보겠습니다.

extension UserDefaults {
    public enum Keys {
        static let email = "email"
    }

    var email: String {
        set {
            set(newValue, forKey: Keys.email)
        }
        get {
            return String(Keys.email)
        }
    }
}

UserDefaults.standard.email = "abc@xyz.com"
let userEmail = UserDefaults.standard.email

UserDefaults 에 email 이란 key를 가지고 데이터를 저장하는 확장코드입니다.

key값이 email로 지정되어있어 email을 바로 저장하고 꺼내서 사용하기 좋습니다.

하지만 , email만 쓰진 않을 겁니다. 다른 Key를 사용하는 데이터도 UserDefaults에서 사용하게 될텐데 email만 key로 사용하는건 너무 극단적인 기능입니다.

extension UserDefaults {
    public enum Keys {
        static let email = "email"
        static let email1 = "email1"
        static let email2 = "email2"
        static let email3 = "email3"
    }

    var email: String {
        set {
            set(newValue, forKey: Keys.email)
        }
        get {
            return String(Keys.email)
        }
    }
    var email1: String {
        set {
            set(newValue, forKey: Keys.email1)
        }
        get {
            return String(Keys.email1)
        }
    }
    var email2: String {
        set {
            set(newValue, forKey: Keys.email2)
        }
        get {
            return String(Keys.email2)
        }
    }
    var email3: String {
        set {
            set(newValue, forKey: Keys.email3)
        }
        get {
            return String(Keys.email3)
        }
    }
}

그렇다고 매번 새로운 key를 추가하고 set get을 통해 데이터를 미리 정의해놓을 수는 없습니다. 할 수는 있지만 이건 고통스럽습니다.

이런 상황을 위해 만들어진게 propertyWrapper 입니다.

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

위 코드에서 가장 중요한 부분은 var wrappedValue 입니다.

wrappedValue는 꼭 필요한 프로퍼티 입니다.

사용

enum Defaults {
    @UserDefault(key:"email")
    static var email: String?

    @UserDefault(key:"email1")
    static var email1: String?

    @UserDefault(key:"email2")
    static var email2: Int?
    
    @UserDefault(key:"email3")
    static var email3: String?
}

훨씬 보기 좋고 간단해졌습니다.

여기서 속성 래퍼에 기본값을 추가할 수도 있습니다.

@propertyWrapper
struct UserDefault<T> {
    let key: String
    private let defaultValue: T

    var wrappedValue: T {
        get {
            return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}
enum Defaults {
    @UserDefault(key:"email",defaultValue:"")
    static var email: String? 
}

...
Defaults.email = "abc@xyz.com"

속성 래퍼를 사용하면 프로젝트에서 불필요한 코드를 많이 줄일 수 있습니다.

propertyWrapper 다른 사용 예시 입니다.

@propertyWrapper
public struct Field<Value: DatabaseValue> {
  public let name: String
  private var record: DatabaseRecord?
  private var cachedValue: Value?

  public init(name: String) {
    self.name = name
  }

  public func configure(record: DatabaseRecord) {
    self.record = record
  }

  public var wrappedValue: Value {
    mutating get {
      if cachedValue == nil { fetch() }
      return cachedValue!
    }

    set {
      cachedValue = newValue
    }
  }

    public var projectedValue: Self {
    get { self }
    set { self = newValue }
  }

  public func flush() {
    if let value = cachedValue {
      record!.flush(fieldName: name, value)
    }
  }

  public mutating func fetch() {
    cachedValue = record!.fetch(fieldName: name, type: Value.self)
  }
}

사용

public struct Person: DatabaseModel {
  @Field(name: "first_name") public var firstName: String
  @Field(name: "last_name") public var lastName: String
  @Field(name: "date_of_birth") public var birthdate: Date
}

let jinswift = Person()
jinswift.firstName = "Jin"
jinswift.lastName = "Swift"
jinswift.birthdate = Date()

jinswift.$firstName.flush()

출처

swift-evolution/0258-property-wrappers.md at main · apple/swift-evolution

iOS Swift : Property Wrappers (@propertyWrapper)

반응형
Comments