楽水

人々の創造が自由に表現できる舞台づくり

Swift

Swiftのプロパティラッパーをわかりやすく解説

投稿日:


Swiftのプロパティには、プロパティラッパーという機能が用意されています。
今回は、Swiftのプロパティラッパーについて以下の観点で解説します。

  • プロパティラッパーとは何か
  • プロパティラッパーの使い方
  • プロパティーラッパーにおけるプロパティの初期化
  • プロパティーラッパーの$プロパティ

参考本
[改訂新版]Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 WEB+DB PRESS plus

プロパティラッパーとは何か

プロパティラッパーとは、プロパティの値の保存方法や計算方法をプロパティラッパーとして定義し、同じ方法を適用できるプロパティを、そのプロパティラッパーでラップすることで開発の効率化を図るというものです。
プロパティラッパーは、構造体やクラス、列挙型として定義することができます。

プロパティラッパーの使い方

それでは、コードを見ながら具体的にプロパティラッパーの使い方について解説します。
まず、以下のコードを見てください。

@propertyWrapper
struct TwelveOrLess {
 private var number: Int
 init() { self.number = 0 }
 var wrappedValue: Int {
  get { return number }
  set { number = min(newValue, 12) }
 }
}

これは、12より大きい数字は12にして返すというプロパティラッパーを構造体として定義したものです。
プロパティラッパーによってラップされた値をwrapped valueといいます。
プロパティラッパーは構造体やクラス、列挙型に@propertyWrapperをつけることで定義することができます。
また、プロパティの値をどう保存するのか、あるいは、計算するのか、その方法を、wrappedValueに対して定義します。
この例の場合、その方法を

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

として定義しています。
続いて、プロパティラッパーでプロパティをラップする方法について見ていきましょう。
次のコードを見てください。

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

この例のように、プロパティの前にプロパティラッパー名を属性(@)としてつけることで、プロパティをプロパティラッパーでラップすることができます。
この例の場合、上で定義したプロパティラッパーTwelveOrLessを、プロパティheightとwidthに以下のように適用しています。
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
このように、プロパティラッパーを適用することで、そこで定義された値の保存方法や計算方法がプロパティに適用されます。
例えば、以下の例のように、12より大きい数字は12にして返すというTwelveOrLessの方法が適用されます。

var rectangle = SmallRectangle()
print(rectangle.height)
// “0”と出力

rectangle.height = 10
print(rectangle.height)
// “10”と出力

rectangle.height = 24
print(rectangle.height)
// “12”と出力

プロパティーラッパーにおけるプロパティの初期化

プロパティーラッパーを意図的に初期化したい場合は、イニシャライザー(init)を使います。
次の例を見てください。

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

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

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

このプロパティラッパーSmallNumberには3つのイニシャライザー、init()、init(wrappedValue: Int)、init(wrappedValue: Int, maximum: Int) が定義されています。
次に、初期化の方法について見ていきましょう。
以下の例を見てください。

struct ZeroRectangle {
 @SmallNumber var height: Int
 @SmallNumber var width: Int
}

var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// “0 0”と出力

この例の場合、イニシャライザーinit()が適用されています。
次に、以下の例の場合、

struct UnitRectangle {
 @SmallNumber var height: Int = 1
 @SmallNumber var width: Int = 1
}

var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// “1 1”と出力

@SmallNumber var height: Int = 1
@SmallNumber var width: Int = 1
によって、イニシャライザーinit(wrappedValue: Int)が適用されています。
最後に、以下の例の場合、

struct NarrowRectangle {
 @SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
 @SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
}

var narrowRectangle = NarrowRectangle()
print(narrowRectangle.height, narrowRectangle.width)
// “2 3”と出力

narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// “5 4”と出力

@SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
@SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
によって、イニシャライザーinit(wrappedValue: Int, maximum: Int) が適用されていることがわかります。

プロパティーラッパーの$プロパティ

次に、プロパティラッパーの射影について説明します。
プロパティーラッパーは、プロパティを、その値の保存方法や計算方法でラップすることでwrapped valueを返すようにするという説明をしてきました。
プロパティーラッパーには、このwrapped valueだけでなく、それを射影したprojected valueを返す付加的な機能があります。
次の例を見てください。

@propertyWrapper
struct SmallNumber {
 var number: Int
 var projectedValue: Bool
 init() {
  self.number = 0
  self.projectedValue = false
 }
 var wrappedValue: Int {
  get { return number }
  set {
   if newValue > 12 {
    number = 12
    projectedValue = true
   } else {
    number = newValue
    projectedValue = false
   }
  }
 }
}

この例は、
12より大きい数字は12にして返すというwrapped valueだけでなく
その処理が行われたかどうかをブール値として返すprojected valueを定義してます。
projected valueを確認したい場合は、以下の例のように、プロパティーラッパーでラップされたプロパティ名の前に$マークをつけることで確認することができます。

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

someStructure.someNumber = 4
print(someStructure.$someNumber)
// “false”と出力

someStructure.someNumber = 55
print(someStructure.$someNumber)
// “true”と出力

この例の場合、$someNumberを指定することでprojected valueを取得しています。

以上、今回はSwiftのプロパティラッパ−について解説しました。

-Swift

執筆者:

関連記事

Swiftのデイニシャリゼーションについてわかりやすく解説

Swiftでは、インスタンスが必要でなくなったとき、リソースを解放するために、自動参照カウント (ARC) という方法でインスタンスの割り当てを解除します。 インスタンスの割り当て解除が行わ …

Swiftのメモリ管理【weakやunownedをわかりやすく解説】

Swiftのプログラムでweakやonwnedというキーワードをみたことはありませんか? これはSwiftが採用しているARCというメモリ管理の仕組みに関係しています。 今回は以下の観点でSwiftの …

Swiftの関数型についてわかりやすく解説

Swift のすべての関数には型があり、関数のパラメータの型と戻り値の型で構成されます。 この型を Swift の他の型と同じように使うことができ、他の関数にパラメータとして関数を渡すことや、関数から …

Swiftクロージャによる強い参照の循環参照とその解決

Swiftのメモリ管理 では、 2 つのクラスインスタンスのプロパティが互いに強い参照を持つことで、どのようにして強い参照の循環参照が生成されるのかを見てきました。 また、強い参照の循環参照を切るため …

Swiftのプロパティ【プロパティオブザーバーなどをわかりやすく解説】

プロパティは値と特定のクラスや構造体、列挙型を結び付けます。 Swiftのプロパティには、値を保持するプロパティだけではなく様々な種類のプロパティがあります。 今回は、Swiftのプロパティについて以 …