Property Wrappers

About Property Delegates Wrappers

Property wrapper는 SwiftUI발표 전 2019년 3월에 Swift forum에서 이야기 되었습니다. Swift Core팀 구성원인 Douglas Gregor는 원래 lazy keyword처럼 언어 기능에 의해 사용자가 접근 할 수 있는 기능이라고 설명 했습니다 (이때는 Property Delegate라고 불렀습니다)
Lazy는 swift를 멋진 언어로 만드는 디자인중 하나 입니다. 속성이 lazy로 선언이 되면 처음 접근 할 때까지 기본값의 초기화가 지연됩니다. 예를 들어 계산 프로퍼티로 래핑된 private 속성을 사용하여 동일한 기능을 직접 구현 할 수 있지만, lazy키워드만으로 쉽게 만들 수 있습니다
struct Structure { // Deferred property initialization with lazy keyword lazy var deferred = ... // Equivalent behavior without lazy keyword private var _deferred: Type? var deferred: Type { get { if let value = _deferred { return value } let initialValue = ... _deferred = initialValue return initialValue } set { _deferred = newValue } } }
Swift
복사
Property Wrappers는 라이브러리 작성자가 lazy와 같은 기능을 직접 구현할 수 있도록 제공합니다.
Property Wrappers를 통해서 만들 수 있는 몇가지 새로운 패턴을 살펴 보면서 이 기능을 사용하는 방법을 이해해보려고 합니다.

Constraining Values

Property Wrapper는 @Lazy, @Atomic, @ThreadSpecific, @Box를 포함한 많은 실용적인 예제를 제공합니다. 하지만 가장 흥미가 간것은 @Constrained입니다.
Swift 표준라이브러리는 정확하고 성능이 좋은 부동소수점 숫자 형식을 제공하며 32, 64 또는 80비트 길이로 만들 수 있습니다.
Swift3이후 부터 사용자가 직접 정의한 부동 소수점 숫자 유형을 구현이 가능한데, 이마저도 미로와 같은 프로토콜 요구사항을 준수해야합니다.
Property Wrapper는 훨씬 적은 노력으로 표준 숫자 형식을 매개변수화 하는 방법을 제공합니다.

Implementing a value clamping property wrapper

Property wrapper는 지정된 범위 내에서 범위를 벗어난 값을 자동으로 "clamps"합니다.
@propertyWrapper struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(initialValue value: Value, _ range: ClosedRange<Value>) { precondition(range.contains(value)) self.value = value self.range = range } var wrappedValue: Value { get { value } set { value = min(max(range.lowerBound, newValue), range.upperBound) } } }
Swift
복사
이제 @Clamping을 사용하여 범위 내의 모델을 만들 수 있습니다. 예를들어 화학 용액의 산도를 0-14 범위 내에서 모델링을 해보겠습니다.
struct Solution { @Clamping(0...14) var pH: Double = 7.0 } let carbonicAcid = Solution(pH: 4.68) // at 1 mM under standard conditions let superDuperAcid = Solution(pH: -1) superDuperAcid.pH // 0
Swift
복사
이 범위를 벗어나는 pH값을 설정하려고 하면 가장 가까운 경계 값(min, max)가 대신 사용됩니다.
Property wrapper구현에서 다른 Property wrapper를 사용할 수 있습니다. 예를들어 이 UnitInterval Property wrapper는 0과 1사이의 값을 제한하기 위해 @claping을 사용합니다.
@propertyWrapper struct UnitInterval<Value: FloatingPoint> { @Clamping(0...1) var wrappedValue: Value = .zero init(initialValue value: Value) { self.wrappedValue = value } }
Swift
복사
그리고 @UnitInterval property wrapper를 이용하여 빨강, 초록, 파랑의 강도를 나타내는 rgb를 정의할 수 있습니다.
struct RGB { @UnitInterval var red: Double @UnitInterval var green: Double @UnitInterval var blue: Double } let cornflowerBlue = RGB(red: 0.392, green: 0.584, blue: 0.929)
Swift
복사

Related Ideas

@Positive / @NonNegative property wrapper that provides the unsigned guarantees to signed integer types.
@NonZero property wrapper that ensures that a number value is either greater than or less than 0.
@Validated or @Whitelisted / @Blacklisted property wrappers that restrict which values can be assigned.

Transforming Values on Property Assignment

사용자로부터 텍스트 입력을 받는것은 앱개발자들 사이에서 영원한 골치 거리입니다. 문자열 인코딩의 징부한 방식에서 텍스트 필드를 통해 코드를 주입하려는 악의적인 시도까지 있습니다. 그러나 개발자가 사용자 생성 콘테츠를 수락 할 때 마주하는 가장 좌절스러운 문제중 하나가 앞 뒤 공백을 다루는 것입니다.
import Foundation URL(string: " https://nshipster.com") // nil (!) ISO8601DateFormatter().date(from: " 2019-06-24") // nil (!) let words = " Hello, world!".components(separatedBy: .whitespaces) words.count // 3 (!)
Swift
복사
leading space는 off-by-one 에러를 통해 url을 모효화 하고 날짜 파서를 혼란시킬 수 있습니다.
Foundation의 trimmingCharacters는 string값의 앞이나 뒤에 있는 공백을 제거하는 편리한 방법을 제공합니다.