🌯

Swift5.1のProperty Wrappersを試す

Swift5.1(Xcode11.0)から新機能Property Wrappersが使えるようになったので試してみる。

Property Wrappersとは

  • プロパティに共通機能を追加できる
  • プロパティへのアクセス方法に関するAttributeをつくるためのAttribute

docs.swift.org - Property Wrappers (opens new window)

A property wrapper adds a layer of separation between code that manages how a property is stored and the code that defines a property.

Attributeとは

  • @がついてるこんなやつ
    • @autoclosure @escaping @convention @available @discardableResult @objc @nonobjc @objcMembers @GKInspectable @UIApplicationMain @NSApplicationMain @NSCopying @NSKeyedArchiverClassName @NSManaged @testable @IBAction @IBOutlet @IBDesignable @IBInspectable
  • Swift5.1のattribute全解説|全27種 (opens new window)

Attributeをつくってみる

// 12以下を保証する
@propertyWrapper
struct TwelveOrLess {

    private var number = 0

    // 必須
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}

Attributeを使ってみる

struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

var rectangle = SmallRectangle()
print(rectangle.height) // "0"

rectangle.height = 13
print(rectangle.height) // "12"

12以外にも対応できるように拡張

Property Wrappersに初期値を与える

@propertyWrapper
struct SmallNumber {
    private var maximum: Int
    private var number: Int

    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, maximum) }
    }

    init() {
        maximum = Int.max
        number = 0
    }
    init(wrappedValue: Int) {
        maximum = Int.max
        number = min(wrappedValue, maximum)
    }
    init(wrappedValue: Int, maximum: Int) {
        self.maximum = maximum
        number = min(wrappedValue, maximum)
    }
}

初期値を設定する

struct Rectangle {
    @SmallNumber var height: Int
    @SmallNumber(wrappedValue: 0, maximum: 10) var width: Int
}

var rectangle = Rectangle()
rectangle.width = 20
print(rectangle.height, rectangle.width) // 0 10

Projected Value

Projected Valueを定義する

@propertyWrapper
struct SmallNumber {
    private var number = 0
    var projectedValue = false
    var wrappedValue: Int {
        get { return number }
        set {
            if newValue > 12 {
                number = 12
                projectedValue = true
            } else {
                number = newValue
                projectedValue = false
            }
        }
    }
}

Projected Valueを参照する($プロパティ名)

struct SomeStructure {
    @SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()

someStructure.someNumber = 4
print(someStructure.$someNumber)
// Prints "false"

someStructure.someNumber = 55
print(someStructure.$someNumber)
// Prints "true"

Property Wrappersを活用した例

marksands/BetterCodable (opens new window)

  • @LossyArray: 配列の一部の要素のデコードに失敗しても成功した要素のみデコードできるようにする
  • @LossyDictionary: @LossyArrayの辞書版
  • @DefaultCodable: Codableに準拠した型のプロパティにデフォルト値を与える
  • @DefaultFalse: DefaultCodableの簡易版、デフォルトでfalseにする
  • @DefaultEmptyArray: DefaultCodableの簡易版、デフォルトで空配列にする
  • @DefaultEmptyDictionary: DefaultCodableの簡易版、デフォルトで空辞書にする
  • @LosslessValue: StringやInt、Boolなどプロパティの型とJSONの型が異なってもデコードできるようにする