버그 잡이

Swift에서 Property Wrapper 시작하기 #UserDefaults # projectedValue 본문

Swift

Swift에서 Property Wrapper 시작하기 #UserDefaults # projectedValue

버그잡이 2020. 11. 28. 17:22

www.avanderlee.com/swift/property-wrappers/

 

Getting started with Property Wrappers in Swift - SwiftLee

Get started with Property Wrappers in Swift. Use the @propertyWrapper to remove boilerplate, improve readability, and clean up code.

www.avanderlee.com

위 글을 보고 정리한 내용임을 밝힙니다.

 


 

Property Wrapper란 무언인가?

property wrapper는 property가 저장되고 계산되는 것을 정의하는 하나의 층으로 볼 수 있습니다.

이는 getter와 setter에 사용되는 반복적인 코드를 대체하는데 유용하게 쓰일 수 있습니다.

가장 일반적인 예로는 user-default를 property-wrapper로 custom 해서 쓰는 예제가 있습니다.

 

 

 

Property wrapper와 UserDefaults

아래 코드르 보면 알 수 있듯이 UserDefault 객체에서 wrapper를 만들고 있습니다.

이로써 string key를 복사 붙여넣기 할 필요없이 해당 property를 사용할 수 있습니다.

 

extension UserDefaults {

    public enum Keys {
        static let hasSeenAppIntroduction = "has_seen_app_introduction"
    }

    /// Indicates whether or not the user has seen the onboarding.
    var hasSeenAppIntroduction: Bool {
        set {
            set(newValue, forKey: Keys.hasSeenAppIntroduction)
        }
        get {
            return bool(forKey: Keys.hasSeenAppIntroduction)
        }
    }
}
UserDefaults.standard.hasSeenAppIntroduction = true

guard !UserDefaults.standard.hasSeenAppIntroduction else { return }
showAppIntroduction()

 

이것은 좋은 해결책으로 보이지만 정의된 key와 property가 많은 경우 이는 좋은 해결책이 아닙니다.

(UserDefault의 내부 코드가 관리하기 힘들 정도로 늘어날 수 있지요)

@propertyWrapper는 이 문제를 해결하는데 도움을 줍니다.

 

 

 

불필요한 코드를 줄이기 위해서 Property Wrapper 사용하기

 

우리는 property Wrapper를 사용해서 반복된 코드를 없앤 UserDefault를 만들 수 있습니다.

@propertyWrapper
struct UserDefault<Value> {
    let key: String
    let defaultValue: Value
    var container: UserDefaults = .standard

    var wrappedValue: Value {
        get {
            return container.object(forKey: key) as? Value ?? defaultValue
        }
        set {
            container.set(newValue, forKey: key)
        }
    }
}

 

wrapper는 아직 값이 등록되지 않아도 default 값을 넘겨줄 수 있게 해줍니다.

우리는 어떤 값이든 generic 값인 Value를 통해서 넘겨줄 수 있습니다.

이를 통해 우리는 이전 코드를 아래와 같이 수정할 수 있습니다.

 

extension UserDefaults {

    @UserDefault(key: "has_seen_app_introduction", defaultValue: false)
    static var hasSeenAppIntroduction: Bool
}

 

위 코드를 보면 prppertyWrapper로 정의된 이니셜라이저로 property를 생성하고 default value를 넘겨줄 수 있음을 확인할 수 있습니다.

 

UserDefaults.hasSeenAppIntroduction = false
print(UserDefaults.hasSeenAppIntroduction) // Prints: false
UserDefaults.hasSeenAppIntroduction = true
print(UserDefaults.hasSeenAppIntroduction) // Prints: true

 

위와 같이 간단하게 사용할 수 있습니다.

같은 wrapper를 활용해서 프로퍼티들을 더 추가할 수 있습니다.

 

extension UserDefaults {

    @UserDefault(key: "has_seen_app_introduction", defaultValue: false)
    static var hasSeenAppIntroduction: Bool

    @UserDefault(key: "username", defaultValue: "Antoine van der Lee")
    static var username: String

    @UserDefault(key: "year_of_birth", defaultValue: 1990)
    static var yearOfBirth: Int
}

 

 

 

projectedValue와 Property Wrapper

property wrapper는 wrapped value에 다른 프로퍼티를 추가할 수 있는 기능이 있습니다.

이는 projectedValue라는 속성을 통해서 가능합니다.

즉, wrappedValue 뿐만 아니라 추가적인 하나의 Value를 더 가질 수 있는 것이죠.

 

@propertyWrapper
struct SampleFile {

    let fileName: String

    var wrappedValue: URL {
        let file = fileName.split(separator: ".").first!
        let fileExtension = fileName.split(separator: ".").last!
        let url = Bundle.main.url(forResource: String(file), withExtension: String(fileExtension))!
        return url
    }

    var projectedValue: String {
        return fileName
    }
}


struct SampleFiles {
    @SampleFile(fileName: "sample-image.png")
    static var image: URL
}


print(SampleFiles.image) // Prints: "../resources/sample-image.png"
print(SampleFiles.$image) // Prints: "sample-image.png"

// $ 기호를 앞에 붙여주면 projectedValue 값을 가져올 수 있습니다.

 

위처럼 "projectedValue" 를 통해서 wrappedValue 뿐만 아니라 다른 추가적인 값도 관리할 수 있습니다.

이 기능은 initial value가 알고 싶을 때,

'$' 와 같은 접두사, 접미사를 붙이고 싶을 때 유용합니다.

 

 

 

정리

1. property wrapper는 불필요한 코드를 줄여주는 하나의 층이다.

2. 이 층에서는 get, set과 관련된 로직 처리가 들어가 wrapping된 property는 get, set 함수를 하나하나 구현할 필요가 없다.

3. projectedValue를 통해서 하나 추가적인 property를 관리할 수 있다.

 

반응형
Comments