楽水

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

Swift

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

投稿日:2020年9月8日 更新日:


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では、関数のスコープ内に便利な機能を包むために、他の関数の中に関数を記述することができます。 ネストされた関数を別のスコープで使用できるように、ネストしている関数からネストされた関数を返すこ …

Swiftの関数のパラメータ【引数ラベルなどについてわかりやすく解説】

Swift 関数には、パラメータ名の無いシンプルな関数から、引数ラベルや各種パラメータがあるメソッドまで表現できる柔軟性があります。 パラメータは、関数の呼び出しを簡略化するためのデフォルト値を持つこ …

Swiftのクロージャのエスケープ【escapingについてわかりやすく解説】

Swiftを学ぶ過程でescapingというキーワードに出会うことがあると思います。 今回は、クロージャのescapingについて以下の観点で丁寧も解説します。 Swiftのクロージャのescapin …

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

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

Swiftのメソッドをわかりやすく解説

メソッドは、特定の型に関連した関数です。 クラス、構造体、列挙型はすべて、型のインスタンスを扱う特定のタスクと機能を持つインスタンスメソッドを定義することができます。 また、クラス、構造体、列挙型は、 …