プロパティ(Properties)

最終更新日: 2023/11/3 原文: https://docs.swift.org/swift-book/LanguageGuide/Properties.html

インスタンスまたは型の一部である、格納および計算された値にアクセスする。

プロパティは、値を特定のクラス、構造体、または列挙型に関連付けます。_格納プロパティ_は定数と変数の値をインスタンスの一部として保存しますが、_計算プロパティ_は値を(保存するのではなく)計算します。計算プロパティは、クラス、構造体、および列挙型で使用できます。格納プロパティは、クラスと構造体のみで使用できます。

格納および計算プロパティは、通常、特定の型のインスタンスに関連付けられています。ただし、プロパティを型自体に関連付けることもできます。このようなプロパティは、_型プロパティ_と呼ばれます。

さらに、_プロパティオブザーバ_を定義して、プロパティの値の変更を監視して、それに応じたアクションを起こすことができます。プロパティオブザーバは、自分で定義した格納プロパティ、およびサブクラスがそのスーパークラスから継承するプロパティに追加できます。

プロパティラッパを用いることで、複数のプロパティの get や set のためのコードを再利用できます。

格納プロパティ(Stored Properties)

最もシンプルな形式だと、_格納プロパティ_は、特定のクラスまたは構造体のインスタンスの一部として保存される定数または変数です。格納プロパティは、変数格納プロパティ(var キーワードを使用)または定数格納プロパティ(let キーワードを使用) のいずれかです。

Default Property Values(プロパティのデフォルト値)で説明されているように、格納プロパティのデフォルト値をその定義の一部として設定できます。また、初期化中に格納プロパティに初期値の設定や変更もできます。これは、Assigning Constant Properties During Initialization(初期化中の定数プロパティへの値の設定)で説明されているように、定数の格納プロパティにも当てはまります。

下記の例では、FixedLengthRange という構造体を定義しています。これは、作成後に範囲の長さを変更できない整数の範囲を表します。

struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// 0, 1, 2 の整数の範囲を表しています
rangeOfThreeItems.firstValue = 6
// 6, 7, 8 の整数の範囲を表しています

FixedLengthRange のインスタンスには、firstValue と呼ばれる変数格納プロパティと length という定数格納プロパティがあります。上記の例では、定数のため、長さは新しい範囲が作成されたときに初期化され、それ以降は変更できません。

定数に割り当てられた構造体のインスタンスの格納プロパティ(Stored Properties of Constant Structure Instances)

構造体のインスタンスを作成し、そのインスタンスを定数に割り当てる場合、インスタンスのプロパティは、変数で宣言されていても変更できません。

let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// 0, 1, 2, 3 の整数の範囲を表しています
rangeOfFourItems.firstValue = 6
// firstValue は変数ですがエラーになります

rangeOfFourItems は定数として(let キーワードを使用して)宣言されているため、firstValue が変数でも、その firstValue プロパティを変更することはできません。

この挙動は、構造体が値型のためです。値型のインスタンスが定数としてマークされている場合、その全てのプロパティも同様です。

参照型のクラスには同じことが当てはまりません。参照型のインスタンスを定数に割り当てた場合でも、そのインスタンスの変数を変更できます。

遅延格納プロパティ(Lazy Stored Properties)

_遅延格納プロパティ_は、最初に使用されるまで初期値が計算されないプロパティです。宣言の前に lazy 修飾子を記述して、遅延格納プロパティを示します。

NOTE インスタンスの初期化が完了するまで初期値が取得できない可能性があるため、遅延プロパティは常に変数として(var キーワードを使用して)宣言する必要があります。定数プロパティは、初期化が完了する前に常に値を持っている必要があるため、lazy で宣言することはできません。

遅延格納プロパティは、プロパティの初期値が、インスタンスの初期化が完了後でないと値がわからない外部要因に依存している場合に役立ちます。プロパティの初期値が必要になるまで実行すべきではない、複雑または計算コストの高いセットアップが必要な場合にも役立ちます。

下記の例では、遅延格納プロパティを使用して、複雑なクラスの不必要な初期化を回避しています。この例では、DataImporterDataManager という 2 つのクラスを定義していますが、どちらも全体像は表示していません:

class DataImporter {
    /*
    DataImporter は、外部ファイルからデータをインポートするためのクラスです
    このクラスは、初期化にかなりの時間がかかると想定します
    */
    var filename = "data.txt"
    // DataImporter クラスは、ここでデータのインポート機能を提供します
}

class DataManager {
    lazy var importer = DataImporter()
    var data: [String] = []
    // DataManager クラスはここでデータの管理機能を提供します
}

let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// importer プロパティの DataImporter インスタンスはまだ作成されていません

DataManager クラスには、data と呼ばれる格納プロパティがあり、新しい空の String 型の配列で初期化されています。残りの機能は示されていませんが、この DataManager クラスの目的は、この String の配列を管理し、アクセスする機能を提供することです。

DataManager クラスの機能の一部は、ファイルからデータをインポートする機能です。この機能は、DataImporter クラスによって提供されています。このクラスは、初期化にかなりの時間がかかると想定されています。これは、DataImporter インスタンスが初期化されるときに、ファイルを開いてその内容をメモリに読み込む必要があるため時間がかかる可能性があります。

DataManager インスタンスはファイルからデータをインポートしなくてもデータを管理できるため、DataManager 自体が作成されるときに、DataManager は新しい DataImporter インスタンスを作成しません。代わりに、DataImporter インスタンスを最初に使用するときに作成する方が理にかなっています。

lazy 修飾子でマークされているため、importer プロパティの DataImporter インスタンスは、importer プロパティが最初にアクセスされたとき(filename プロパティが照会されたときなど)にのみ作成されます。

print(manager.importer.filename)
// importer プロパティの DataImporter インスタンスが作成されました
// data.txt

NOTE lazy 修飾子でマークされたプロパティが複数のスレッドから同時にアクセスされ、そのプロパティがまだ初期化されていない場合、そのプロパティが 1 回だけ初期化されるという保証はありません。

格納プロパティとインスタンス変数(Stored Properties and Instance Variables)

Objective-C の経験がある場合は、クラスインスタンスの一部として値と参照を格納する 2 つの方法が提供されていることを知っているかもしれません。プロパティに加えて、インスタンス変数をプロパティの保存領域として使用できます。

Swift は、これらの概念を単一のプロパティ宣言に統合しました。Swift のプロパティには対応するインスタンス変数がなく、プロパティの保存領域に直接アクセスされることはありません。このアプローチにより、様々なコンテキストから値にアクセスされてしまうことで起きる混乱を回避し、プロパティの宣言方法を単一の明確な文に単純化します。名前、型、メモリ管理の特性など、プロパティに関する全ての情報は、型の定義の一部として 1 つの場所に集約されています。

計算プロパティ(Computed Properties)

格納プロパティに加えて、クラス、構造体、および列挙型は、_計算プロパティ_を定義できます。これは値を保持せず、代わりに間接的に他のプロパティの値を取得する get を提供します。他のプロパティに値を設定する set を提供することもできます。

struct Point {
    var x = 0.0, y = 0.0
}
struct Size {
    var width = 0.0, height = 0.0
}
struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
                  size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// square.origin is now at (10.0, 10.0)

この例では、幾何学的形状を操作するための 3 つの構造体を定義しています。

  • Point は、x 座標と y 座標をカプセル化しています

  • Sizewidthheight をカプセル化しています

  • Rect は、原点とサイズで四角形を定義しています

Rect 構造体は、center と呼ばれる計算プロパティも提供します。Rect の現在の中心位置は常にその originsize から判断できるため、中心点を明示的に Point として保存する必要はありません。代わりに、Rectcenter と呼ばれる計算プロパティ変数の独自の get/set を定義して、あたかも格納プロパティかのように四角形の中心を操作できるようにします。

上記の例では、square という新しい Rect 変数を作成しています。square 変数は、原点 (0, 0)、幅と高さ 10 で初期化されます。この正方形は、下の図では青い正方形で表されています。

次に、ドット構文(square.center)を介して Square 変数の center プロパティにアクセスします。これにより、center の get が呼び出され、現在のプロパティ値が取得できます。get は、既存の値を返すのではなく、正方形の中心を表す新しい Point を計算して返します。上記のように、get は (5, 5) の中心点を正しく返します。

次に、center プロパティに新しく (15, 15) が設定されます。これにより、下の図のオレンジ色の正方形で示されている新しい位置に正方形が上下に移動します。center プロパティを設定すると、保存されている origin プロパティの xy の値を変更する center の set が呼び出され、正方形が新しい位置に移動します。

省略プロパティ set 宣言(Shorthand Setter Declaration)

計算プロパティの set が、設定される新しい値の名前を定義しない場合、デフォルト名の newValue が使用されます。下記はこの省略表記を利用した Rect 構造体の代替バージョンを次に示します:

struct AlternativeRect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

省略プロパティ get 宣言(Shorthand Getter Declaration)

get の本文全体が単一式の場合、暗黙的にその式を返します。この省略表記を利用した別のバージョンの Rect 構造体を次に示します:

struct CompactRect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            Point(x: origin.x + (size.width / 2),
                  y: origin.y + (size.height / 2))
        }
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

get からの戻り値の省略は、Functions With an Implicit Return(暗黙的な戻り値がある関数)で説明されているように、関数からの戻り値を省略した場合と同じ規則に従います。

読み取り専用計算プロパティ(Read-Only Computed Properties)

get のみの計算プロパティは、_読み取り専用計算プロパティ_と呼ばれます。読み取り専用計算プロパティは常に値を返し、ドット構文を介してアクセスできますが、別の値を設定することはできません。

NOTE (読み取り専用を含む)計算プロパティは、その値が固定されていないため、var キーワードを使用して変数プロパティとして宣言する必要があります。let キーワードは定数プロパティにのみ使用され、インスタンスの初期化の一部として設定された後は値を変更できないことを示します。

get キーワードとその中括弧({})を削除することで、読み取り専用計算プロパティの宣言を簡素化できます。

struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double {
        return width * height * depth
    }
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// the volume of fourByFiveByTwo is 40.0

この例では、Cuboid という新しい構造体を定義します。これは、widthheightdepth プロパティを持つ 3D の長方形のボックスを表します。この構造体には、volume と呼ばれる読み取り専用計算プロパティもあります。これは、直方体の現在のボリュームを計算して返します。特定のボリューム値に widthheightdepth のどの値が使用されているかがあいまいなため、volume に set 可能にすることは意味がありません。それでも、Cuboid が読み取り専用計算プロパティを提供して、外部ユーザーが現在の計算済みボリュームを見られようにすると便利です。

プロパティオブザーバ(Property Observers)

_プロパティオブザーバ_は、プロパティの値の変化を監視し、それに応じたアクションをすることができます。プロパティオブザーバは、新しい値がプロパティの現在の値と同じ場合でも、プロパティの値が設定されるたびに呼び出されます。

次の場所にプロパティオブザーバを追加できます。

  • 自身で定義した格納プロパティ

  • 継承した格納プロパティ

  • 継承した計算プロパティ

継承したプロパティの場合、サブクラスでそのプロパティをオーバーライドすることにより、プロパティオブザーバを追加します。自身で定義した計算プロパティの場合、オブザーバを作成する代わりに、set を使用して値の変更を監視し、応答します。プロパティのオーバーライドについては、Overriding(オーバーライド)で説明されています。

プロパティにこれらのオブザーバの下記のいずれかまたは両方を定義できます:

  • willSet は、値が格納される直前に呼び出されます

  • didSet は、新しい値が格納された直後に呼び出されます

willSet オブザーバを実装すると、新しいプロパティ値が定数パラメータとして渡されます。willSet 実装の一部として、このパラメータの名前を指定できます。実装内にパラメータ名と括弧を記述しない場合、パラメータは newValue というのデフォルトのパラメータ名で使用可能になります。

同様に、didSet オブザーバを実装する場合、古いプロパティ値を含む定数パラメータが渡されます。パラメータに名前を付けるか、oldValue というデフォルトパラメータ名を使用できます。独自の didSet オブザーバ内のプロパティに値を割り当てると、新しい値によって、設定されたばかりの値が置き換えられます。

NOTE スーパークラスのプロパティの willSet および didSet オブザーバは、プロパティがサブクラスのイニシャライザで設定されると、スーパークラスのイニシャライザが呼び出された後に呼び出されます。スーパークラスのイニシャライザが呼び出される前に、サブクラスが独自のプロパティを設定している間は呼び出されません。 イニシャライザの委譲については、Initializer Delegation for Value Types(値型のイニシャライザの委譲)Initializer Delegation for Class Types(クラス型のイニシャライザの委譲)を参照ください。

willSetdidSet の使用例を次に示します。下記の例では、StepCounter という名前の新しいクラスを定義しています。これは、人の合計歩数を追跡します。このクラスは、万歩計からの入力データやその他の日常生活での運動を追跡する歩数計に使われます。

class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("About to set totalSteps to \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue  {
                print("Added \(totalSteps - oldValue) steps")
            }
        }
    }
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps

StepCounter クラスは、Int 型の totalSteps プロパティを宣言します。これは、willSet および didSet オブザーバを持つ格納プロパティです。

プロパティに新しい値が割り当てられるたびに、totalStepswillSet および didSet オブザーバが呼び出されます。これは、新しい値が現在の値と同じでも呼び出されます。

この例の willSet オブザーバは、次の新しい値に newTotalSteps のカスタムパラメータ名を使用します。この例では、設定しようとしている値を出力するだけです。

didSet オブザーバは、totalSteps の値が更新された後に呼び出されます。これは、totalSteps の新しい値を古い値と比較します。合計ステップ数が増えると、新しく何ステップ増えたかを示すメッセージが出力されます。didSet オブザーバは古い値のカスタムパラメータ名を提供せず、代わりに oldValue のデフォルト名が使用されます。

NOTE オブザーバを持つプロパティを関数に入出力パラメータとして渡すと、willSet および didSet オブザーバが常に呼び出されます。これは、入出力パラメータのコピーインコピーアウト(copy-in copy-out)メモリモデルによるものです。値は常に関数の最後でプロパティに書き戻されます。入出力パラメータの動作の詳細については、In-Out Parameters(In-Out パラメータ)を参照ください。

プロパティラッパ(Property Wrappers)

_プロパティラッパ_は、プロパティの格納方法を管理するコードと、プロパティを定義するコードとの間に境界レイヤを追加します。例えば、スレッドセーフチェックを提供するプロパティ、またはその基になるデータをデータベースに格納するプロパティがある場合、全てのプロパティにそのコードを記述する必要があります。プロパティラッパを使用すると、ラッパを定義するときに管理コードを 1 回記述し、その管理コードを複数のプロパティに適用して再利用できます。

プロパティラッパを定義するには、wrappedValue プロパティを定義する構造体、列挙型、またはクラスを作成します。下記のコードでは、TwelveOrLess 構造体により、ラップする値に常に 12 以下の数値が含まれることが保証されます。より大きな数値を格納するように要求すると、代わりに 12 が格納されます。

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

set は新しい値が 12 未満だと確認し、get は格納された値を返します。

NOTE 上記の例の number の宣言は、変数を private としてマークします。これにより、numberTwelveOrLess の実装でのみ使用されます。それ以外の場所に記述されたコードは、wrappedValue のプロパティの get/set を通して値にアクセスし、数値を直接使用することはできません。private については、Access Control(アクセスコントロール)を参照ください。

プロパティラッパを適用するには、属性としてプロパティの前にラッパの名前を記述します。TwelveOrLess プロパティラッパを使用して、大きさが常に 12 以下になるようにする四角形を格納する構造体を次に示します。

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

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

rectangle.height = 10
print(rectangle.height)
// 10

rectangle.height = 24
print(rectangle.height)
// 12

heightwidth のプロパティは、TwelveOrLess.number を 0 に設定する TwelveOrLess の定義から初期値を取得します。TwelveOrLess の set は、10 を有効な値として扱うため、数値 10rectangle.height に格納すると、記述どおりに進みます。ただし、24TwelveOrLess が許容する値よりも大きいため、24 を格納しようとすると、代わりに rectangle.height を最大許容値の 12 に設定することになります。

プロパティラッパを適用すると、コンパイラは、ラッパにストレージを提供するコードと、ラッパを介したプロパティへのアクセスするためのコードの 2 つを生成します。(プロパティラッパ自体にはラップされた値を格納する責務があり、コンパイラが生成するコードはありません) 特別な属性構文を利用せずに、プロパティラッパの挙動を実現するコードを作成することもできます。例えば、前のコードリストの SmallRectangle に、属性として @TwelveOrLess を記述する代わりに、TwelveOrLess 構造体でそのプロパティを明示的にラップしています。

struct SmallRectangle {
    private var _height = TwelveOrLess()
    private var _width = TwelveOrLess()
    var height: Int {
        get { return _height.wrappedValue }
        set { _height.wrappedValue = newValue }
    }
    var width: Int {
        get { return _width.wrappedValue }
        set { _width.wrappedValue = newValue }
    }
}

_height および _width プロパティは、プロパティラッパ TwelveOrLess のインスタンスを格納します。heightwidth の get/set は、wrappedValue プロパティへのアクセスをラップします。

ラップされたプロパティの初期値の設定(Setting Initial Values for Wrapped Properties)

上記の例のコードは、TwelveOrLess の定義で number に初期値を与えることにより、ラップされたプロパティの初期値を設定します。このプロパティラッパを使用するコードでは、TwelveOrLess でラップされたプロパティに異なる初期値を指定できません。例えば、SmallRectangle の定義では heightwidth の初期値を指定できません。初期値の設定やその他のカスタマイズをサポートするには、プロパティラッパでイニシャライザを追加する必要があります。これは、ラップされた値と最大値を設定するイニシャライザを定義する SmallNumber と呼ばれる TwelveOrLess の拡張バージョンです。

@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 の定義には、init()init(wrappedValue:)、および init(wrappedValue:maximum:) の 3 つのイニシャライザが含まれています。下記の例では、これらのイニシャライザを使用して、ラップされた値と最大値を設定します。イニシャライザとその構文については、Initialization(初期化)を参照ください。

プロパティラッパを適用し、初期値を指定しない場合、Swift は init() を使用してラッパを設定します。例えば:

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

var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// 0 0

heightwidth をラップする SmallNumber のインスタンスは、SmallNumber() を呼び出すことによって作成されます。そのイニシャライザ内のコードは、0 と 12 のデフォルト値を使用して、ラップされた値の初期値と最大値の初期値を設定します。プロパティラッパは、SmallRectangleTwelveOrLess を使用した前の例のように、引き続き全ての初期値を設定します。その例とは異なり、SmallNumber は、プロパティの宣言の一部としてこれらの初期値を書き込むこともサポートしています。

プロパティの初期値を指定すると、Swift は init(wrappedValue:) イニシャライザを使用してラッパを設定します。例えば:

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

var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// 1 1

ラッパを使用してプロパティに = 1 を書き込むと、それは init(wrappedValue:) イニシャライザの呼び出しに変換されます。heightwidth をラップする SmallNumber のインスタンスは、SmallNumber(wrappedValue: 1) を呼び出すことによって作成されます。イニシャライザは、ここで指定されたラップされた値を使用し、デフォルトの最大値の 12 を使用します。

カスタム属性の後の括弧内に引数を記述すると、Swift はそれらの引数を受け入れるイニシャライザを使用してラッパを設定します。たとえば、初期値と最大値を指定すると、Swift は init(wrappedValue:maximum:) イニシャライザを使用します:

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

height をラップする SmallNumber のインスタンスは SmallNumber(wrappedValue: 2, maximum: 5) を呼び出すことで作成され、width をラップするインスタンスは SmallNumber(wrappedValue: 3, maximum: 4) を呼び出すことで作成されます。

プロパティラッパに引数を含めることで、ラッパの初期状態を設定したり、ラッパの作成時に他のオプションをラッパに渡すことができます。この構文は、プロパティラッパを使用する最も一般的な方法です。必要な引数を属性に指定でき、それらはイニシャライザに渡されます。

プロパティラッパに引数を含める場合、代入で初期値を指定することもできます。Swift は、この値の代入を wrappedValue 引数のように扱い、含まれる引数を受け入れるイニシャライザを使用します。例えば:

struct MixedRectangle {
    @SmallNumber var height: Int = 1
    @SmallNumber(maximum: 9) var width: Int = 2
}

var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// 1

mixedRectangle.height = 20
print(mixedRectangle.height)
// 12

height をラップする SmallNumber のインスタンスは、デフォルトの最大値 12 を使用する SmallNumber(wrappedValue: 1) を呼び出すことによって作成されます。width をラップするインスタンスは、SmallNumber(wrappedValue: 2, maximum: 9) を呼び出すことによって作成されます。

Property Wrapperからの値の投影(Projecting a Value From a Property Wrapper)

プロパティラッパは、ラップされた値に加えて、projectedValue を定義することで追加機能を公開できます。例えば、データベースへのアクセスを管理するプロパティラッパは、その projectedValue で flushDatabaseConnection() メソッドを提供できます。projectedValue の名前は、ドル記号 ($) で始まることを除いて、ラップされた値と同じです。コードでは $ で始まるプロパティを定義できないため、projectedValue が定義したプロパティにコードから干渉することはありません。

上記の SmallNumber の例では、プロパティを大きすぎる数値に設定しようとすると、プロパティラッパは数値を格納する前に調整します。下記のコードは、projectedValue プロパティを SmallNumber 構造体に追加して、プロパティラッパが新しい値を格納する前にプロパティの新しい値を調整したかどうかを追跡します。

@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
            }
        }
    }
}
struct SomeStructure {
    @SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()

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

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

someStructure.$someNumber を書き込むと、ラッパの projectedValue にアクセスします。4 などの小さな数を格納した後、someStructure.$someNumber の値は false です。ただし、55 などの大きすぎる数値を格納しようとすると、projectedValue は true になります。

プロパティラッパは、projectedValue として任意の型の値を返すことができます。この例では、プロパティラッパは、数値が調整されたかどうかに関係なく、1 つの情報のみを公開するため、そのブール値をその projectedValue として公開します。より多くの情報を公開する必要があるラッパは、他の型のインスタンスを返すことも、self を返してラッパのインスタンスをその projectedValue として公開することもできます。

get やインスタンスメソッドなど、型の一部から投影された値にアクセスする場合、他のプロパティにアクセスするのと同じように、プロパティ名の前の self. を省略できます。次の例のコードは、heightwidth のラッパの projectedValue を $height および $width として参照します。

enum Size {
    case small, large
}

struct SizedRectangle {
    @SmallNumber var height: Int
    @SmallNumber var width: Int

    mutating func resize(to size: Size) -> Bool {
        switch size {
        case .small:
            height = 10
            width = 20
        case .large:
            height = 100
            width = 100
        }
        return $height || $width
    }
}

プロパティラッパの構文は、get/set の単なる糖衣構文(シンタックスシュガー)なので、heightwidth へのアクセスは、他のプロパティへのアクセスと同じように働きます。例えば、resize(to:) のコードは、プロパティラッパを使用して heightwidth にアクセスします。resize(to: .large) を呼び出すと、.large の switch の case により、長方形の高さと幅が 100 に設定されます。ラッパはそれぞれのプロパティが 12 を超えないようにするため、値を調整したことを記録し、projectedValue に true が設定されます。resize(to:) の最後で、return ステートメントは $height$width をチェックして、プロパティラッパが height または width のいずれかを調整したかどうかを判断します。

グローバル変数とローカル変数(Global and Local Variables)

プロパティを計算および監視するための上記の機能は、グローバル変数とローカル変数でも使用できます。グローバル変数は、関数、メソッド、クロージャ、または型コンテキストなどの外部で定義される変数です。ローカル変数は、関数、メソッド、またはクロージャコンテキスト内で定義される変数です。

前の章で登場したグローバル変数とローカル変数は全て格納変数でした。格納プロパティと同様に、格納変数は、特定の型の値を保存し、その値を設定および取得できるようにします。

グローバルスコープまたはローカルスコープのいずれでも、計算変数を定義したり、格納変数のオブザーバを定義することもできます。計算変数は、値を保存するのではなく計算し、計算プロパティと同じ方法で記述できます。

NOTE グローバル定数と変数は、遅延格納プロパティと同様の方法で、常に必要なったら計算されます。遅延格納プロパティとは異なり、グローバル定数と変数は lazy 修飾子でマークする必要はありません。 ローカル定数と変数は決して遅延して計算されません。

プロパティラッパは、ローカルに保存された変数に適用できますが、グローバル変数または計算変数には適用できません。例えば、下記のコードでは、myNumberSmallNumber をプロパティラッパとして使用します。

func someFunction() {
    @SmallNumber var myNumber: Int = 0

    myNumber = 10
    // myNumber は 10

    myNumber = 24
    // myNumber は 12
}

プロパティに小数を適用する場合と同様に、myNumber を 10 に設定することは有効です。プロパティラッパは 12 を超える値を許可しないため、myNumber を 24 ではなく 12 に設定します。

型プロパティ(Type Properties)

インスタンスプロパティは、特定の型のインスタンスに属するプロパティです。その型の新しいインスタンスを作成するたびに、他のインスタンスとは別のプロパティ値のセットがあります。

その型のインスタンスではなく、型自体に属するプロパティを定義することもできます。その型のインスタンスをいくつ作成しても、これらのプロパティのコピーは 1 つしかありません。このようなプロパティは、_型プロパティ_と呼ばれます。

型プロパティは、全てのインスタンスが使用できる(C 言語の静的定数のような)定数プロパティや、全てのインスタンスにグローバルな値を格納する(C 言語の静的変数のような)変数プロパティなど、特定の型の全てのインスタンスに共通の値を定義するのに役立ちます。

格納型プロパティは、変数または定数にすることができます。計算型プロパティは、計算インスタンスプロパティと同じ方法で、常に変数プロパティとして宣言されます。

NOTE 格納インスタンスプロパティとは異なり、格納型プロパティには常にデフォルト値を指定する必要があります。これは、型の初期化時に格納型プロパティに値を割り当てることができるイニシャライザがないためです。 格納型プロパティは、最初のアクセス時に遅延して初期化されます。それらは、複数のスレッドから同時にアクセスされた場合でも、1 回だけ初期化されることが保証されており、lazy 修飾子でマークする必要はありません。

型プロパティ構文(Type Property Syntax)

C 言語および Objective-C では、静的定数および型に関連付けられた変数をグローバル静的変数として定義します。Swift では、型プロパティは型の定義の一部として、型定義の中括弧内({})に記述され、各型プロパティは、サポートする型に明示的にスコープされます。

static キーワードを使用して型プロパティを定義します。クラス型の計算型プロパティの場合、代わりに class キーワードを使用して、サブクラスがスーパークラスの実装をオーバーライドできるようにすることもできます。下記の例は、格納および計算型プロパティの構文を示しています。

struct SomeStructure {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 1
    }
}
enum SomeEnumeration {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 6
    }
}
class SomeClass {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 27
    }
    class var overrideableComputedTypeProperty: Int {
        return 107
    }
}

NOTE 上記の計算型プロパティの例は、読み取り専用の計算型プロパティですが、計算インスタンスプロパティと同じ構文を使用して、読み書き可能な計算型プロパティを定義することもできます。

型プロパティへのアクセスと設定(Querying and Setting Type Properties)

型プロパティは、インスタンスプロパティと同様に、ドット構文でアクセスおよび設定できます。ただし、型プロパティは、その型のインスタンスではなく、その型自体に対してクエリおよび設定します。例えば:

print(SomeStructure.storedTypeProperty)
// Some value.
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// Another value.
print(SomeEnumeration.computedTypeProperty)
// 6
print(SomeClass.computedTypeProperty)
// 27

下記の例では、いくつかのオーディオチャネルのオーディオレベルメータをモデル化する構造体の一部として、2 つの格納型プロパティを使用します。各チャンネルには、0 から 10 までの整数のオーディオレベルがあります。

次の図は、これらのオーディオチャネルの 2 つを組み合わせてステレオオーディオレベルメータをモデル化する方法を示しています。チャンネルのオーディオレベルが 0 の場合、そのチャンネルのライトはどれも点灯しません。オーディオレベルが 10 の場合、そのチャンネルの全てのライトが点灯します。この図では、左チャネルの現在のレベルは 9 で、右チャネルの現在のレベルは 7 です。

上記のオーディオチャネルは、AudioChannel 構造体のインスタンスによって表されます。

struct AudioChannel {
    static let thresholdLevel = 10
    static var maxInputLevelForAllChannels = 0
    var currentLevel: Int = 0 {
        didSet {
            if currentLevel > AudioChannel.thresholdLevel {
                // 新しいオーディオ レベルをしきい値レベルに制限します
                currentLevel = AudioChannel.thresholdLevel
            }
            if currentLevel > AudioChannel.maxInputLevelForAllChannels {
                // これを新しい全体の最大入力レベルとして保存します
                AudioChannel.maxInputLevelForAllChannels = currentLevel
            }
        }
    }
}

AudioChannel 構造体は、その機能をサポートする 2 つの格納型プロパティを定義します。最初の thresholdLevel は、オーディオレベルの最大のしきい値を定義します。これは、全ての AudioChannel インスタンスの定数値 10 です。オーディオ信号が 10 よりも高い値で入力されると、このしきい値に制限されます (後述)。

2 番目の型のプロパティは、maxInputLevelForAllChannels と呼ばれる変数格納型プロパティです。これは、AudioChannel インスタンスによって受信された最大入力値を追跡します。初期値 0 から始まります。

AudioChannel 構造体は、0 から 10 のスケールでチャネルの現在のオーディオレベルを表す、currentLevel と呼ばれる保存されたインスタンスプロパティも定義します。

currentLevel プロパティには、設定されるたびに currentLevel の値をチェックする didSet プロパティオブザーバがあります。このオブザーバは、次の 2 つのチェックを実行します:

  • currentLevel の新しい値が許可された thresholdLevel より大きい場合、プロパティオブザーバは currentLevelthresholdLevel に制限します

  • (しきい値でレベルを制限した後の) currentLevel の新しい値が、AudioChannel インスタンスによって以前に受信された値よりも大きい場合、プロパティオブザーバは新しい currentLevel 値を maxInputLevelForAllChannels 型プロパティに保存します

NOTE これらの 2 つのチェックのうち最初のチェックでは、didSet オブザーバは currentLevel を別の値に設定します。ただし、これによってオブザーバが再度呼び出されることはありません。

AudioChannel 構造体を使用して、ステレオサウンドシステムのオーディオレベルを表す、leftChannel および rightChannel という 2 つの新しいオーディオチャネルを作成できます。

var leftChannel = AudioChannel()
var rightChannel = AudioChannel()

左チャネルの currentLevel7 に設定すると、maxInputLevelForAllChannels 型プロパティが 7 に更新されることがわかります。

leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// 7
print(AudioChannel.maxInputLevelForAllChannels)
// 7

右チャネルの currentLevel11 に設定しようとすると、右チャネルの currentLevel プロパティが最大値 10 に制限され、maxInputLevelForAllChannels 型プロパティが 10 に更新されることがわかります。

rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// 10
print(AudioChannel.maxInputLevelForAllChannels)
// 10

最終更新