[Swift] Property Wrapperを理解する 0258-property-wrappersの写経 日本語訳

SwiftのPropertyWrapperを理解できてきた気がするけど、その本質からの理解を得たくて、SE(Swift Evolution)を日本語化して写経してみる
Swift[Swift] PropertyWrapper 手を動かして理解する(その1 wrappedValue) Swift[Swift] PropertyWrapper 手を動かして理解する(その2 wrappedValue の初期化) [Swift] PropertyWrapper 手を動かして理解する(その3 $ もしくは projectedValue の理解)

# まだ用語がブレてます。推敲していくことで統一予定。

いうまでもなく、オリジナルは、こちら

Introduction/導入

プロパティを実装するパターンは、いくつかあって、繰り返し利用される。特定の固定コードをコンパイラーに入れ込むよりは、ライブラリとして定義されたこれらのパターンを
一般化した"Property Wrapper"のメカニズムとして提供する方が良い。

2015-106 property behaviorsでの方向性とは反対のアプローチとなる。
いくつかの例は同じだが、この提案は、デザインがよりシンプルになり、ユーザーに理解しやすく、コンパイラの実装も大きな変更を要さない。
以下のセクションでは、この設計の本質的な相違点について議論している。

Pitch #1

Pitch #2

Pitch #3

Motivation/動機

これまでに、lazy や @NSCopying のようなプロパティーに対してのいくつかの重要なパターンを言語でサポートしようとしてきた、
しかし、これらは、非常に狭い領域でのユーティリティだった。
たとえば、Swiftは lazy プロパティを言語の基本機能として備えている、なぜなら、遅延初期化は一般的でありプロパティを optional として外部提供してしまうことを避けるために必要だからである。このサポートなしには、同様の効果を得るためには、非常に多くの定型的なコードが必要になる。

アクセスがあったら値設定

struct Foo {
  // lazy var foo = 1738
  private var _foo: Int?
  var foo: Int {
    get {
      if let value = _foo { return value }
      let initialValue = 1738
      _foo = initialValue
      return initialValue
    }
    set {
      _foo = newValue
    }
  }
}

lazy を言語に取り込むことで、いくつかの不利益もあります。言語とコンパイラを複雑にしますし、直交性が犠牲となりました。また、柔軟性も失われました。遅延初期化については、何種類かのバリエーションがありましたが、全てをサポートは、しませんでした。

遅延初期化以外に重要なプロパティのパターンがあります。「一度設定して以降変更不可」を「遅延」させておこなうマルチフェーズ初期化です。

一度設定したら以降変更不可&設定するまでアクセス不可

class Foo {
  let immediatelyInitialized = "foo"
  var _initializedLater: String?

  // We want initializedLater to present like a non-optional 'let' to user code;
  // it can only be assigned once, and can't be accessed before being assigned.
  var initializedLater: String {
    get { return _initializedLater! }
    set {
      assert(_initializedLater == nil)
      _initializedLater = newValue
    }
  }
}

ここでは、Implicitly-unwrapped optionls(IUO)を使うことで実現されていますが、optionalでないletを使うことに比べて、多くの安全性が失われています。
マルチフェーズ初期化をIUOを使って実装することは、不変性とnil-安全性を失ってしまいます。

アトリビュート @NSCopying を導入したことで、変数設定時のコピー作成に、NSCopying.copy()を使うこととなりました。実装パターンは、これと似たものになるかもしれません。

NSCopying.copyパターン

class Foo {
  // @NSCopying var text: NSAttributedString
  var _text: NSAttributedString
  var text: NSAttributedString {
    get { return _text }
    set { _text = newValue.copy() as! NSAttributedString }
  }
}

Proposed solution/提案

"Property wrapper"の導入を提案します。それを使うことで、プロパティ定義に置いて、どのwrapperを使って実装するかを宣言できます。この wrapper はアトリビュートとして記述されます。

アトリビュート記述をもつプロパティfoo

@Lazy var foo = 1738

この行によりプロパティ foo は、以下の"Property wrapper" Lazy として記述された方法で実装されます。

コード

@propertyWrapper
enum Lazy {
  case uninitialized(() -> Value)
  case initialized(Value)

  init(wrappedValue: @autoclosure @escaping () -> Value) {
    self = .uninitialized(wrappedValue)
  }

  var wrappedValue: Value {
    mutating get {
      switch self {
      case .uninitialized(let initializer):
        let value = initializer()
        self = .initialized(value)
        return value
      case .initialized(let value):
        return value
      }
    }
    set {
      self = .initialized(newValue)
    }
  }
}

"Property wrapper type" は、ラッパーとして使われるプロパティのストレージを提供します。
Wrapper タイプの wrapperdValue プロパティは、ラッパーの実際の実装になります。optionalの init(wrappedValue:)によって、Valueタイププロパティを初期化することが可能です。

コード

@Lazy var foo = 1738

上記は、以下のようなコードになります。

コード

private var _foo: Lazy = Lazy(wrappedValue: 1738)
var foo: Int {
  get { return _foo.wrappedValue }
  set { _foo.wrappedValue = newValue }
}

Prefix _ は、同期ストレージプロパティ名として意図的に使われていて、同期ストレージプロパティの名前を事前合意していることで、private なプロパティを問題なく定義できます。例えば、Lazy で初期値に戻すための reset(_:)を定義することもできます。

コード

extension Lazy {
  /// Reset the state back to "uninitialized" with a new,
  /// possibly-different initial value to be computed on the next access.
  mutating func reset(_ newValue:  @autoclosure @escaping () -> Value) {
    self = .uninitialized(newValue)
  }
}

_foo.reset(42)

背後にあるプロパティを明示的に初期化することができます。

コード

extension Lazy {
  init(body: @escaping () -> Value) {
    self = .uninitialized(body)
  }
}

func createAString() -> String { ... }

@Lazy var bar: String  // not initialized yet / 初期化されてない
_bar = Lazy(body: createAString)

"Property wrapper"のインスタンスは、名前の後にカッコとイニシャライザの引数を指定することで直接初期化できます。
上記のコードは、以下の1行の変数定義のコードと同値になります。

コード

@Lazy(body: createAString) var bar: String

"Property wrapper"は、グローバル/ローカル もしくは、タイプスコープにかかわらず、使えます。それらプロパティは、監視アクサッサ(willSet/didSet)を持つことができますが、明示的にgetter/setterを書く必要はありません。

"Lazy property wrapper"は、初期化以外にほとんどAPIを持ちませんので、外部に公開する意味は少ないかもしれません。
しかし、"property wrapper"は、興味深いAPIをもつ要素との関係を記述することができます。例えば、名前によって定義されたデータベースフィールドを参照する property wrapper を使うかもしれません。

コード

@propertyWrapper
public struct Field {
  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 func flush() {
    if let value = cachedValue {
      record!.flush(fieldName: name, value)
    }
  }
  
  public mutating func fetch() {
    cachedValue = record!.fetch(fieldName: name, type: Value.self)
  }
}

モデルを上記の Field property wrapper を使って定義できます。

コード

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
}

Field そのものが、Person を使うユーザーにとって重要なAPIになっています。使うことで、値をflushしたり、新しい値をfetchしたり、データベースの対応するフィールドの名前を取得することもできます。しかし、アンダースコア付きの変数(_firstName, _lastName, _birthdate)は、privateであり、直接操作することはできません。

APIとして使えるようにするために、property wrapper "Field"は、"projection"を提供し、それ経由でデータベースとのフィールドの関係性を操作できます。
"Projection" プロパティは、前置詞として、”$"を使います。ですので、firstName プロパティは、 $firstName として使われ、firstName が参照可能な場所では、$firstName も参照可能です。"property wrapper"は、projetionを、projectedValueを定義することにより提供します。

コード

@propertyWrapper
public struct Field {
  // ... API as before ...
  
  public var projectedValue: Self {
    get { self }
    set { self = newValue }
  }
}

projectedValue が存在する場合は、projection 変数が、projectedValueをwrappするプロジェクション変数が作られます。例えば、以下のプロパティ

コード

@Field(name: "first_name") public var firstName: String

は、以下のように展開されます。

コード

private var _firstName: Field = Field(name: "first_name")

public var firstName: String {
  get { _firstName.wrappedValue }
  set { _firstName.wrappedValue = newValue }
}

public var $firstName: Field {
  get { _firstName.projectedValue }
  set { _firstName.projectedValue = newValue }
}

このように展開されることによって、"property"と"projection"の両方を操作することができるようになります。

コード

somePerson.firstName = "Taylor"
$somePerson.flush()

Examples/例

詳細設計について説明する前に、いくつかの wrapper の例を提示する。

Delayed Initialization/遅延初期化

"Property wrapper”は、遅延初期化としてつられ、そこでは、コンパイル時にではなくより動的に明確な初期化ルールが強制されます。このことで、マルチフェーズ初期化の際に、IUO(Implicitly-unwrapped option)を避けることができます。変更可能(再設定可能なvarを使って)・変更不可(再設定できないletを使って)のいずれの変数も定義することができます。

Mutableバージョン

@propertyWrapper
struct DelayedMutable {
  private var _value: Value? = nil

  var wrappedValue: Value {
    get {
      guard let value = _value else {
        fatalError("property accessed before being initialized")
      }
      return value
    }
    set {
      _value = newValue
    }
  }

  /// "Reset" the wrapper so it can be initialized again.
  mutating func reset() {
    _value = nil
  }
}
immutableバージョン

@propertyWrapper
struct DelayedImmutable {
  private var _value: Value? = nil

  var wrappedValue: Value {
    get {
      guard let value = _value else {
        fatalError("property accessed before being initialized")
      }
      return value
    }

    // Perform an initialization, trapping if the
    // value is already initialized.
    set {
      if _value != nil {
        fatalError("property initialized twice")
      }
      _value = newValue
    }
  }
}

上記の定義を使うことで、マルチフェーズ初期化が可能となります。

マルチフェーズ初期化

class Foo {
  @DelayedImmutable var x: Int

  init() {
    // We don't know "x" yet, and we don't have to set it
  }

  func initializeX(x: Int) {
    self.x = x // Will crash if 'self.x' is already initialized
  }

  func getX() -> Int {
    return x // Will crash if 'self.x' wasn't initialized
  }
}

NSCopying

多くのCocoaのクラスでは、value-likeなオブジェクトを使っていて、それは、明示的なコピーを必要とします。Swiftでは、@NSCopying アトリビュートを提供していて、Objective-C の @property(copy) のような動作をさせることが可能となっています。プロパティにセットされたときに、新しい Object で copy method が呼ばれます。この振る舞いを wrapper にすることができます。

NSCopying相当

@propertyWrapper
struct Copying {
  private var _value: Value
  
  init(wrappedValue value: Value) {
    // Copy the value on initialization.
    self._value = value.copy() as! Value
  }

  var wrappedValue: Value {
    get { return _value }
    set {
      // Copy the value on reassignment.
      _value = newValue.copy() as! Value
    }
  }
}

この実装は、SE-0153で説明されている問題を解決します。copy() を init(wrappedValue:) の外側で使うことで、意味的には、SE-0153 の解決策となります。

(訳注:SE-0153ざっくり説明:イニシャライザの中で、setter経由でアクセスしないのがベストプラクティス)

Atomic/一連操作

一連操作(load, store, 増減, 比較して変更)は Swift に一般的に要求される機能である。
それら機能を実装することは、Compilerや標準ライブラリを魔法のようにしてしまうが、インターフェイス自身は、property wrapper として提供することができる。

コード

@propertyWrapper
struct Atomic {
  private var _value: Value
  
  init(wrappedValue: Value) {
    self._value = wrappedValue
  }

  var wrappedValue: Value {
    get { return load() }
    set { store(newValue: newValue) }
  }
  
  func load(order: MemoryOrder = .relaxed) { ... }
  mutating func store(newValue: Value, order: MemoryOrder = .relaxed) { ... }
  mutating func increment() { ... }
  mutating func decrement() { ... }
}

extension Atomic where Value: Equatable {
  mutating func compareAndExchange(oldValue: Value, newValue: Value, order: MemoryOrder = .relaxed)  -> Bool { 
    ...
  }
}  

enum MemoryOrder {
  case relaxed, consume, acquire, release, acquireRelease, sequentiallyConsistent
};

以下は、Atomic の単純なユースケース。Atomic タイプを使うことで、ローレベル操作を組み合わせることは非常に一般的で、そこでは、シンプルな操作を使いますが、(メモリへのアクセス順番のような)ここの意味を考える必要があります。Property と 同キストレージプロパティは、よく使われます。

コード

@Atomic var counter: Int

if thingHappened {
  _counter.increment()
}
print(counter)

@Atomic var initializedOnce: Int?
if initializedOnce == nil {
  let newValue: Int = /*computeNewValue*/
  if !_initializedOnce.compareAndExchange(oldValue: nil, newValue: newValue) {
    // okay, someone else initialized it. clean up if needed
  }
}
print(initializedOnce)

Thread-specific storage/Thread 向け Storage

Thread 向け Storage (pthreads ベース) も property wrapper で実装できます。(Daniel Delwood より)

コード

@propertyWrapper
final class ThreadSpecific {
  private var key = pthread_key_t()
  private let initialValue: T

  init(key: pthread_key_t, wrappedValue: T) {
    self.key = key
    self.initialValue = wrappedValue
  }

  init(wrappedValue: T) {
    self.initialValue = wrappedValue
    pthread_key_create(&key) {
      // 'Any' erasure due to inability to capture 'self' or 
      $0.assumingMemoryBound(to: Any.self).deinitialize(count: 1)
      $0.deallocate()
    }
  }

  deinit {
    fatalError("\(ThreadSpecific.self).deinit is unsafe and would leak")
  }

  private var box: UnsafeMutablePointer {
    if let pointer = pthread_getspecific(key) {
      return pointer.assumingMemoryBound(to: Any.self)
    } else {
      let pointer = UnsafeMutablePointer.allocate(capacity: 1)
      pthread_setspecific(key, UnsafeRawPointer(pointer))
      pointer.initialize(to: initialValue as Any)
      return pointer
    }
  }

  var wrappedValue: T {
    get { return box.pointee as! T }
    set (v) {
      box.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee = v }
    }
  }
}

User Defaults

Property wrapper は、プロパティを、string-key データに変換することに使うことができます。以下、user defaults(Harlan Haskins より)の例で、
そこでは、wrapper type にデータを展開するためのメカニズムを作っています。

コード

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

enum GlobalSettings {
  @UserDefault(key: "FOO_FEATURE_ENABLED", defaultValue: false)
  static var isFooFeatureEnabled: Bool
  
  @UserDefault(key: "BAR_FEATURE_ENABLED", defaultValue: false)
  static var isBarFeatureEnabled: Bool
}

Copy-on-write

Ref/Box

"Clamping" a value within bounds

Property wrapper types in the wild

Composition of property wrappers/ property wrapper の合成

property に複数の property wrapper が記述されたときには、wrapper は、まとめられ、いずれも効果を持ちます。例えば、以下は DelayedMutable と Copying の合成です。

コード

@DelayedMutable @Copying var path: UIBezierPath

初期化を後にすることができるプロパティで、値をセットしたときには、NSCopying の copy を使ってコピーが実行されるプロパティになります。

合成は、後ろにある wrapper type が前にある wrapper type の内側になるように階層化されて実装されます。もっとも内側にあるタイプは、オリジナルのプロパティタイプとなります。例えば、上記の例では、DelayedMutable<Copying<UIBezierPath>>というタイプになります。path へのgetter/setter は、2つのレベルの.wrappedValueを使うことになります。

注記:この設計では、property wrapper の合成は、可換ではありません。なぜなら、順番によって影響が異なるからです。

コード

@DelayedMutable @Copying var path1: UIBezierPath   // _path1 has type DelayedMutable>
@Copying @DelayedMutable var path2: UIBezierPath   // error: _path2 has ill-formed type Copying>

このケースでは、タイプチェックにより、2番目の順番は使えません、なぜなら DelayedMutable は NSCopying に準拠していないからです。
いつでもこのようなことにできるわけではありません。意味的におかしいだけであるならば、タイプチェックによって検出できないかもしれません。
この合成アプローチに対する別案は、"Alternatives considered"で説明されています。

Detailed design/詳細設計

Property wrapper types/Property wrapper types

"property wrapper type" は、Property wrapper として使えるタイプです。"property wrapper type"への基本的な要件は以下の2つです。

1. "Property wrapper type"は、アトリビュート @propertyWrapper と一緒に定義されなければいけません。アトリビュートは、property wrapper type として使われることを示し、コンパイラーに様々なルールをチェックするタイミングを与えます。
2. "Property wrapper type"は、wrappedValue という名前のプロパティを持たなければいけません。そのアクセスレベルは、タイプ自身のものと同じです。これは、wrap された内部の値にアクセスするときにコンパイラによって使われます。

Initialization of synthesized storage properties/同期ストレージプロパティの初期化

Property wrapper を導入することで、(setter/getterを持つ)計算プロパティと wrap されるタイプを持つ格納型プロパティを持つことになります。格納型プロパティは、以下の3つの方法のうちの1つで初期化することができます。

1. オリジナルのプロパティのタイプのイニシャライザ(例:@Lazy var foo: Int のケースでのIntにあたり、init(wrappedValue:)を使います)そのイニシャライザは、1つのwrappedValue プロパティと同型のパラメータを受け取るはずです。アクセスレベルは、property wrapper type と同じです。init(wrappedValue:) が存在するならば、プロパティ定義で使うことができます。以下は例です。

コード

@Lazy var foo = 17

// ... implemented as
private var _foo: Lazy = Lazy(wrappedValue: 17)
var foo: Int { /* access via _foo.wrappedValue as described above */ }

複数の合成された property wrapper の場合は、その全てが、init(wrappedValue:)を用意する必要があり、最終的なイニシャライザは、全てのレベルの呼び出しを wrap します。


@Lazy @Copying var path = UIBezierPath()

// ... implemented as
private var _path: Lazy> = .init(wrappedValue: .init(wrappedValue: UIBezierPath()))
var path: UIBezierPath { /* access via _path.wrappedValue.wrappedValue as described above */ }

2. property wrapper type の値を使って初期化, property wrapper typeをイニシャライザの引数後に配置するということ

コード

var addressOfInt: UnsafePointer = ...

@UnsafeMutablePointer(mutating: addressOfInt) 
var someInt: Int

// ... implemented as
private var _someInt: UnsafeMutablePointer = UnsafeMutablePointer(mutating: addressOfInt)
var someInt: Int { /* access via _someInt.wrappedValue */ }

複数の合成された property wrapper の場合は、一番外側の wrapper だけが、イニシャライザを持つかもしれません。

3. イニシャライザがなく、property wrapper type が引数のないイニシャライザ(init())を提供するときは、wrapper type の init() が格納型プロパティを初期化するために呼ばれます。

コード

@DelayedMutable var x: Int

// ... implemented as
private var _x: DelayedMutable = DelayedMutable()
var x: Int { /* access via _x.wrappedValue */ }

複数の合成された property wrapper の場合は、一番外側の wrapper だけが、イニシャライザ( init() )を持つ必要があります。

Type inference with property wrappers / property wrapper でのタイプ推論

最初のプロパティラッパーが generic である時は、その generic 情報は、明示的に与えられるか、Swift がその変数定義から推測できなければいけない。その推測は以下のように行われる。

変数が、初期値 E を与えられている時は、A(wrapperValue: E, argsA...)の呼び出し結果のタイプと同じとなる、そこでは、A はアトリビュートのタイプであり、argsA は、そのアトリビュートに渡される引数である。以下は、例。

コード

@Lazy var foo = 17
// type inference as in...
private var _foo: Lazy = Lazy(wrappedValue: 17)
// infers the type of '_foo' to be 'Lazy'  _foo は Lazyと推測される

もし複数のラッパアトリビュートが存在するならば、呼び出しの引数は、ネストされる。以下は、例。

コード

@A @B(name: "Hello") var bar = 42
// type inference as in ...
private var _bar = A(wrappedValue: B(wrappedValue: 42, name: "Hello"))
// infers the type of '_bar' to be 'A'  _bar は、A> と推測される

上記ではなく、最初のラッパー亜トリビュートが、直接の初期化引数 E...を持つならば、一番外側のラッパータイプは、A(E...)と同じタイプになる、そこでは、A は、最初に書かれたアトリビュートのタイプとなる。最初に書かれたものの後にあるラッパアトリビュートは、直接のInitializerを持っていないかもしれない。以下は、例。

コード

@UnsafeMutablePointer(mutating: addressOfInt)
var someInt
// type inference as in...
private var _someInt: UnsafeMutablePointer = UnsafeMutablePointer.init(mutating: addressOfInt)
// infers the type of `_someInt` to be `UnsafeMutablePointer`  _someInt は UnsafeMutablePointer と推測される

上記ではなく、初期化されずに、オリジナルのプロパティが、タイプ指定をもっているならば、wrappedValue プロパティのタイプは、オリジナルプロパティのタイプと同じとなる。以下は例。

コード

@propertyWrapper
struct Function {
  var wrappedValue: (T) -> U? { ... }
}

@Function var f: (Int) -> Float?   // infers T=Int, U=Float    T は、Int, U は Float と推論される

いずれの場合でも、最初のラッパタイプは、アトリビュートに記述された最初の指定となる。さらにいうと、2つ目以降のラッパアトリビュートについては、その前のラッパータイプのwrappedValue プロパティのタイプになる。最終的に、もし、タイプ指定があるならば、最後のラッパータイプのwrappedValue プロパティのタイプは、その指定と一致する。もしこれらのルールで推測できない、もしくは、不整合が発生するならば、その変数定義は正しくないということになる。以下は、例。

コード

@Lazy var foo: Int  // okay オーケー
@Lazy var bar: Double  // error: Lazy.wrappedValue is of type Int, not Double :エラー Lazy.wrappedValue は Int であり、Double でないので不正

その推測プロセスは、オリジナルのタイプ指定が省略されていても、オリジナルプロパティのタイプ情報を提供する、もしくは、タイプ指定から省略された generic を提供する。以下は、例。

コード

@propertyWrapper
struct StringDictionary {
  var wrappedValue: [String: String]
}

@StringDictionary var d1.            // infers Dictionary
@StringDictionary var d2: Dictionary // infers 

Custom attributes

プロパティラッパーは、一種のカスタムアトリビュートであり、アトリビュートの文法は、Swift で定義される要素へと参照されるために使われる。
文法的には、プロパティラッパーは、以下のように記述される。

コード

attribute ::= '@' type-identifier expr-paren?

Type-identifier は、プロパティラッパータイプを参照しなければいけなく、そこに generic を含むことができる。このことにより、アトリビュート名に修正を入れることができるようになる。以下は、例。

コード

@Swift.Lazy var foo = 1742

Expr-paren は、(もし存在するならば)、ラッパーインスタンスの初期化のための引数となる。

このカスタムアトリビュートの定式化は、lerger proposal for custom attributes と齟齬がなく、そこでも上記と同様のカスタムアトリビュートのための文法が使われているだけでなく、アトリビュートとして使用できるためのタイプを定義する別の方法も言及されている。このやり方では、@propertyWrapper は、一種のカスタムアトリビュートであり、コンパイル時にのみや実行時のみに有効なカスタムアトリビュートも存在すると考えられる。

Mutability of properties with wrappers

一般に、プロパティは、getter と setter をもつプロパティラッパーを持つ。ただし、プロパティタイプの wrappedValue プロパティに setter がない時やアクセスできないときには、プロパティタイプの setter もないかもしれない。

プロパティラッパーの wrappedValue プロパティが、mutating であり、struct の一部であるならば、同期getter が mutating となる。
同様に、プロパティラッパーの wrappedValue プロパティが nonmutating であるか、プロパティラッパーが class であるならば、同期 setter は nonmutating となる。以下は、例。

コード

@propertyWrapper
struct MutatingGetterWrapper {
  var wrappedValue: Value {
    mutating get { ... }
    set { ... }
  }
}

@propertyWrapper
struct NonmutatingSetterWrapper {
  var wrappedValue: Value {
    get { ... }
    nonmutating set { ... }
  }
}

@propertyWrapper
class ReferenceWrapper {
  var wrappedValue: Value
}

struct Usewrappers {
  // x's getter is mutating
  // x's setter is mutating
  @MutatingGetterWrapper var x: Int

  // y's getter is nonmutating
  // y's setter is nonmutating
  @NonmutatingSetterWrapper var y: Int
  
  // z's getter is nonmutating
  // z's setter is nonmutating
  @ReferenceWrapper var z: Int  
}

訳注:とりあえず、ここまで

誤訳, Typo 等、お気づきの点ありましたら、教えてください。

残り

Out-of-line initialization of properties with wrappers

Memberwise initializers

Codable, Hashable, and Equatable synthesis

$ identifiers

Projections

Restrictions on the use of property wrappers

Impact on existing code

Backward compatibility

Alternatives considered

Composition

Composition via nested type lookup

using a formal protocol instead of @property wrapper

Kotlin-like by syntax

Alternative spellings for the $ projection property

The 2015-2016 property behaviors design

Future Directions

Finer-grained access control

Referencing the enclosing 'self' in a wrapper type

Delegating to an existing property

Revisions

Acknowledgements

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です