初期化(Initialization)

最終更新日: 2023/09/20 原文: https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

型に格納されたプロパティの初期値を設定し、一度きりのセットアップを実行します。

_初期化_は、使用するクラス、構造体、または列挙型のインスタンスを準備するプロセスです。このプロセスには、そのインスタンスに保存されている各プロパティの初期値の設定と、新しいインスタンスの使用準備が整う前に必要なその他の要素のセットアップや初期化の実行が含まれます。

この初期化プロセスを実装するには、_イニシャライザ_を定義します。イニシャライザは、特定の型の新しいインスタンスを作成するために呼び出すことができる特別なメソッドのようなものです。Objective-C のイニシャライザとは異なり、Swift のイニシャライザは値を返しません。主な役割は、型の新しいインスタンスが初めて使用される前に正しく初期化されていることを保証することです。

クラス型のインスタンスは、そのクラスのインスタンスが解放される直前に独自のクリーンアップを実行する_デイニシャライザ_を実装することもできます。デイニシャライザの詳細については、Deinitialization(デイニシャライザ)を参照ください。

格納プロパティの初期値の設定(Setting Initial Values for Stored Properties)

クラスと構造体は、そのクラスまたは構造体のインスタンスが作成されるまでに、全ての格納プロパティに適切な初期値を設定する必要があります。格納プロパティは、不確定な状態のままにすることはできません。

イニシャライザ内、またはプロパティの定義の一部としてプロパティのデフォルト値を割り当てることにより、格納プロパティの初期値を設定できます。これらのアクションについては、次のセクションで説明します。

NOTE 格納プロパティにデフォルト値を割り当てるか、イニシャライザ内でその初期値を設定すると、プロパティオブザーバを呼び出さずに、そのプロパティに値が直接設定されます。

イニシャライザ(Initializers)

_イニシャライザ_は、特定の型の新しいインスタンスを作成するために呼び出されます。最もシンプルな形式では、イニシャライザはパラメータのないインスタンスメソッドのようなもので、init キーワードを使用して記述されます:

init() {
    // ここで初期化を実行します
}

下記の例では、華氏で表された温度を保存するために Fahrenheit という新しい構造体を定義しています。Fahrenheit 構造体には、Double 型の temperature という 1 つの格納プロパティがあります。

struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}
var f = Fahrenheit()
print("温度の初期値は華氏 \(f.temperature)°")
// 温度の初期値は華氏 32.0°

この構造体はパラメータのない単一のイニシャライザ init を定義します。これは、格納された温度を 32.0(華氏での水の凝固点)の値で初期化します。

プロパティのデフォルト値(Default Property Values)

上記のように、イニシャライザ内から格納プロパティの初期値を設定できます。または、プロパティの宣言の一部として、定義時に初期値を割り当てることで_デフォルトプロパティ値_を指定します。

NOTE プロパティが常に同じ初期値を取る場合は、イニシャライザ内で値を設定するのではなく、デフォルト値を指定します。最終結果は同じですが、デフォルト値の方がプロパティの初期化と宣言をより密接に結び付けています。これにより、イニシャライザが短く明確になり、デフォルト値からプロパティの型を推論できるようになります。また、デフォルト値を使用すると、この章で後述するように、デフォルトのイニシャライザとイニシャライザの継承を利用しやすくなります。

プロパティが宣言された時点で temperature プロパティのデフォルト値を指定することにより、上記の Fahrenheit 構造体をよりシンプルな形式で書くことができます。

struct Fahrenheit {
    var temperature = 32.0
}

初期化のカスタマイズ(Customizing Initialization)

次のセクションで説明するように、入力パラメータやオプショナル型のプロパティを使用したり、初期化中に定数プロパティを割り当てることにより、初期化プロセスをカスタマイズできます。

イニシャライザのパラメータ(Initialization Parameters)

初期化パラメータをイニシャライザの定義の一部として提供することで、初期化プロセスをカスタマイズするための値の型と名前を定義できます。初期化パラメータには、関数およびメソッドパラメータと同じ機能と構文があります。

次の例では、摂氏で表された温度を格納する Celsius という構造体を定義しています。Celsius 構造体は、init(fromFahrenheit:) および init(fromKelvin:) という 2 つのカスタムイニシャライザを実装しています。これらは、構造体の新しいインスタンスを異なる温度スケールの値で初期化しています。

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
}
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius は 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius は 0.0

最初のイニシャライザには、fromFahrenheit という引数ラベルと fahrenheit というパラメータ名を持つ単一のパラメータがあります。2 番目のイニシャライザには、fromKelvin という引数ラベルと kelvin というパラメータ名を持つ単一のパラメータがあります。どちらのイニシャライザも、単一のパラメータを対応する摂氏の値に変換し、この値を temperatureInCelsius というプロパティに格納しています。

パラメータ名と引数ラベル(Parameter Names and Argument Labels)

関数やメソッドのパラメータと同様に、初期化パラメータには、イニシャライザの本文内で使用するパラメータ名と、イニシャライザを呼び出すときに使用する引数ラベルの両方を含めることができます。

ただし、イニシャライザには、関数やメソッドのように括弧の前に識別関数名がありません。したがって、イニシャライザのパラメータの名前と型は、どのイニシャライザを呼び出す必要があるかを識別する上で特に重要な役割を果たします。このため、Swift は、イニシャライザが提供されていない場合、イニシャライザの全てのパラメータに自動で引数ラベルを提供します。

次の例では、redgreenblue という 3 つの定数プロパティを持つ Color という構造体を定義しています。これらのプロパティには、色の赤、緑、青の量を示す 0.01.0 の値が格納されます。

Color は、赤、緑、および青のコンポーネント用に Double 型の適切に名前が付けられた 3 つのパラメータを含むイニシャライザを提供します。Color は、3 つ全ての色コンポーネントに同じ値を提供するために使用される、単一の white パラメータを持つ 2 番目のイニシャライザも提供します。

struct Color {
    let red, green, blue: Double
    init(red: Double, green: Double, blue: Double) {
        self.red   = red
        self.green = green
        self.blue  = blue
    }
    init(white: Double) {
        red   = white
        green = white
        blue  = white
    }
}

両方のイニシャライザを使用して、各イニシャライザのパラメータに名前付きの値を指定することで、新しい Color インスタンスを作成できます。

let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)

引数ラベルを使用せずにこれらのイニシャライザを呼び出すことはできないことに注意してください。引数ラベルが定義されている場合は、常にイニシャライザで使用する必要があり、それらを省略するとコンパイルエラーになります。

let veryGreen = Color(0.0, 1.0, 0.0)
// 引数ラベルが必要になるため、コンパイルエラーになります

引数ラベルのないイニシャライザパラメータ(Initializer Parameters Without Argument Labels)

初期化パラメータに引数ラベルを使用したくない場合は、そのパラメータの明示的な引数ラベルの代わりにアンダースコア (_) を記述して、デフォルトの挙動をオーバーライドします。

これは、上記のInitialization Parameters(イニシャライザのパラメータ)Celsius の例の拡張バージョンで、すでに摂氏スケールにある Double 値から新しい Celsius インスタンスを作成する追加のイニシャライザを備えています。

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
    init(_ celsius: Double) {
        temperatureInCelsius = celsius
    }
}
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius は 37.0

イニシャライザの呼び出し Celsius(37.0) は、意図が明確なので引数ラベルを必要としません。したがって、このイニシャライザを init(_ celsius: Double) と記述して、名前のない Double 値を指定して呼び出すことができるのは妥当です。

オプショナルのプロパティ型(Optional Property Types)

独自に定義した型に、論理的に「値なし」が許される格納プロパティがある場合(おそらく、初期化中にその値を設定できないか、後で「値なし」になる可能性があるため)、プロパティをオプショナル型を使って宣言できます。オプショナル型のプロパティは、値 nil で自動的に初期化されます。こうすることで、プロパティが初期化中に意図的に「まだ値がない」ことを示すことができます。

次の例では、SurveyQuestion というクラスを定義し、オプショナルの String プロパティとして response を定義しています。

class SurveyQuestion {
    var text: String
    var response: String?
    init(text: String) {
        self.text = text
    }
    func ask() {
        print(text)
    }
}
let cheeseQuestion = SurveyQuestion(text: "チーズは好きですか?")
cheeseQuestion.ask()
// チーズは好きですか?
cheeseQuestion.response = "ええ、大好きです。"

アンケートの質問への回答は質問されるまでわからないため、回答プロパティは String? または Optional<String> で宣言されています。SurveyQuestion の新しいインスタンスが初期化されると、デフォルト値の nil、つまり「文字列はまだありません」が自動的に割り当てられます。

初期化中の定数プロパティの割り当て(Assigning Constant Properties During Initialization)

初期化が完了するまでに明確な値が設定されている限り、初期化中の任意の時点で定数プロパティに値を割り当てることができます。定数プロパティに値が割り当てられると、それ以上変更することはできません。

NOTE クラスインスタンスの場合、初期化中に定数プロパティを変更できるのは、それを導入したクラスだけです。サブクラスでは変更できません。

上記の SurveyQuestion の例を修正して、質問の text プロパティに変数プロパティではなく定数プロパティを使用して、SurveyQuestion のインスタンスが作成されたら質問が変更されないことを示すことができます。text プロパティは定数ですが、クラスのイニシャライザ内で設定できます。

class SurveyQuestion {
    let text: String
    var response: String?
    init(text: String) {
        self.text = text
    }
    func ask() {
        print(text)
    }
}
let beetsQuestion = SurveyQuestion(text: "ビーツはどうでしょう?")
beetsQuestion.ask()
// ビーツはどうでしょう?
beetsQuestion.response = "ビーツも好きですよ。(でもチーズと一緒にはしないでください。)"

デフォルトイニシャライザ(Default Initializers)

Swift は、全てのプロパティにデフォルト値を設定し、1 つもイニシャライザを提供しない構造体またはクラスに対して、デフォルトのイニシャライザを提供します。デフォルトのイニシャライザは、全てのプロパティがデフォルト値に設定された新しいインスタンスを作成するだけです。

この例では、ショッピングリスト内のアイテムの名前、数量、購入状態をカプセル化する ShoppingListItem というクラスを定義しています。

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()

ShoppingListItem クラスの全てのプロパティにはデフォルト値があり、スーパークラスを持たない基本クラスのため、ShoppingListItem は、全てのプロパティにデフォルト値が設定された新しいインスタンスを作成するイニシャライザの実装を自動で獲得します(name プロパティはオプショナルの String プロパティのため、この値がコードに記述されていなくても、デフォルト値の nil が自動的に設定されます)。上記の例では、ShoppingListItem クラスのデフォルトイニシャライザを使用して ShoppingListItem() と記述し、新しいインスタンスを作成して item という変数に割り当てています。

構造体のメンバワイズイニシャライザ(Memberwise Initializers for Structure Types)

構造体は、独自のカスタムイニシャライザを定義していない場合、メンバワイズイニシャライザを自動的に定義されます。デフォルトイニシャライザとは異なり、構造体は、デフォルト値を持たないプロパティが格納されている場合でも、メンバワイズイニシャライザが定義されます。

メンバワイズイニシャライザは、新しい構造体インスタンスのプロパティを初期化する簡単な方法です。新しいインスタンスのプロパティの初期値は、プロパティ名によってメンバワイズイニシャライザに渡すことができます。

下記の例では、widthheight という 2 つのプロパティを持つ Size という構造体を定義しています。両方のプロパティは、デフォルト値 0.0 を割り当てることにより、Double 型だと推論されます。

Size 構造体は、新しい Size インスタンスを初期化するために使用できるメンバワイズイニシャライザ init(width:height:) が自動的に定義されています。

struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

メンバワイズイニシャライザを呼び出すときは、デフォルト値を持つ全てのプロパティの値を省略できます。上記の例では、Size 構造体の heightwidth の両方のプロパティにデフォルト値があります。プロパティのいずれかまたは両方を省略できます。イニシャライザは、省略した全てのプロパティにデフォルト値を使用します。例えば、次のようになります:

let zeroByTwo = Size(height: 2.0)
print(zeroByTwo.width, zeroByTwo.height)
// 0.0 2.0

let zeroByZero = Size()
print(zeroByZero.width, zeroByZero.height)
// 0.0 0.0

値型のイニシャライザの委譲(Initializer Delegation for Value Types)

イニシャライザは、他のイニシャライザを呼び出して、インスタンスの初期化の一部を実行できます。_イニシャライザの委譲_と呼ばれるこのプロセスは、複数のイニシャライザ間でコードが重複するのを防ぎます。

イニシャライザの委譲がどのように機能するか、および許可される委譲の形式に関する規則は、値型とクラス型で異なります。値型(構造体と列挙型)は継承をサポートしていないため、イニシャライザの委譲プロセスは比較的簡単です。ただし、Inheritance(継承)で説明されているように、クラスは他のクラスを継承できます。これは、クラスが継承した全ての格納プロパティに、初期化中に適切な値が割り当てられることを保証する追加の責任があることを意味します。これらの責任は、下記のClass Inheritance and Initialization(クラスの継承と初期化)で説明されています。

値型の場合、独自のカスタムイニシャライザを作成するときに、self.init を使用して同じ値型から他のイニシャライザを参照します。self.init は、イニシャライザ内からのみ呼び出すことができます。

値型のカスタムイニシャライザを定義すると、その型のデフォルトイニシャライザ(または構造体の場合はメンバワイズイニシャライザ)にアクセスできなくなることに注意してください。この制約は、より複雑なイニシャライザで初期化に必須の設定をしている場合に、誤って自動イニシャライザを使用することでイニシャライザが呼ばれない(=初期化が完全にできない)状況を防ぎます。

NOTE カスタムの値型をデフォルトイニシャライザとメンバワイズイニシャライザ、および独自のカスタムイニシャライザで初期化できるようにする場合は、値型の元の実装の一部としてではなく、extension にカスタムイニシャライザを記述します。詳細については、Extension(拡張)を参照ください。

次の例では、幾何学的な四角形を表すカスタム Rect 構造体を定義しています。この例では、SizePoint という 2 つの補助となる構造体が必要です。どちらも、全てのプロパティにデフォルト値 0.0 を提供します:

struct Size {
    var width = 0.0, height = 0.0
}
struct Point {
    var x = 0.0, y = 0.0
}

下記の 3 つの方法のいずれかで、Rect 構造体を初期化できます。つまり、0 で初期化されたデフォルトの originsize のプロパティ値を使用するか、特定の原点とサイズを指定するか、特定の中心点とサイズを指定します。これらの初期化の選択肢は、Rect 構造体の定義の一部として 3 つのカスタムイニシャライザで表されます:

struct Rect {
    var origin = Point()
    var size = Size()
    init() {}
    init(origin: Point, size: Size) {
        self.origin = origin
        self.size = size
    }
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2)
        let originY = center.y - (size.height / 2)
        self.init(origin: Point(x: originX, y: originY), size: size)
    }
}

最初の Rect イニシャライザ init() は、独自のカスタムイニシャライザがない場合に受け取るデフォルトのイニシャライザと機能的に同じです。このイニシャライザには、空の中括弧 {} のペアで表される空の本文があります。このイニシャライザを呼び出すと、原点とサイズのプロパティが両方ともプロパティ定義の Point(x: 0.0, y: 0.0)Size(width: 0.0, height: 0.0) のデフォルト値で初期化された Rect インスタンスが返されます:

let basicRect = Rect()
// basicRect の origin は (0.0, 0.0) で size は (0.0, 0.0)

2 番目の Rect イニシャライザ init(origin:size:) は、独自のカスタムイニシャライザがない場合に構造体に自動で定義されるメンバワイズイニシャライザと機能的に同じです。このイニシャライザは、単に origin および size 引数の値を適切な格納されたプロパティに割り当てます:

let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
                      size: Size(width: 5.0, height: 5.0))
// originRect の origin は (2.0, 2.0) で size は (5.0, 5.0)

3 番目の Rect イニシャライザ init(center:size:) は、もう少し複雑です。centersize 値に基づいて適切な原点を計算することから始めます。次に、init(origin:size:) イニシャライザを呼び出し(委譲)します。このイニシャライザは、適切なプロパティに新しい原点とサイズの値を保存します。

let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
                      size: Size(width: 3.0, height: 3.0))
// centerRect の origin は (2.5, 2.5) で size は (3.0, 3.0)

init(center:size:) イニシャライザを使っても、originsize の新しい値を適切なプロパティに割り当てることができます。ただし、init(center:size:) イニシャライザは、適切な機能をすでに提供している既存のイニシャライザを利用する方が便利(そして意図がより明確)です。

NOTE init() および init(origin:size:) イニシャライザを、定義の外で記述する別の方法については、Extension(拡張)を参照ください。

クラスの継承と初期化(Class Inheritance and Initialization)

サブクラスがスーパークラスから継承する全てのプロパティを含む、クラスの全ての格納プロパティには、初期化中に初期値を割り当てる必要があります。

Swift は、クラス型に対して 2 種類のイニシャライザを定義して、全ての格納プロパティが確実に初期値を受け取るようにします。これらは、_指定イニシャライザ_および _convenience イニシャライザ_と呼ばれます。

指定イニシャライザとconvenience イニシャライザ(Designated Initializers and Convenience Initializers)

_指定イニシャライザ_は、クラスの主要なイニシャライザです。指定イニシャライザは、そのクラスで導入された全てのプロパティを完全に初期化し、適切なスーパークラスのイニシャライザを呼び出して、スーパークラスへチェーンして初期化プロセスを続行します。

複数の指定イニシャライザを持つクラスはあまりなく、1 つだけ持つのが一般的です。指定イニシャライザは、初期化が行われる「漏斗」のような地点で、初期化プロセスはスーパークラスへチェーンして上に向かって続行します。

全てのクラスには、少なくとも 1 つの指定イニシャライザが必要です。場合によっては、この要件は、以下のAutomatic Initializer Inheritance(自動イニシャライザの継承)で説明されているように、スーパークラスから 1 つ以上の指定イニシャライザを継承することで満たされます。

convenience イニシャライザは二次的なもので、クラスのイニシャライザをサポートします。convenience イニシャライザを定義して、指定イニシャライザのパラメータの一部をデフォルト値に設定し、convenience イニシャライザから指定イニシャライザを呼び出すことができます。また、特定のユースケースまたは入力値の型用にそのクラスのインスタンスを作成するために、convenience イニシャライザを定義することもできます。

クラスで必要ない場合は、convenience イニシャライザを提供する必要はありません。共通の初期化パターンへのショートカットとして時間の節約になったり、初期化の意図をより明確にできる場合は、常に convenience イニシャライザを作成してください。

指定イニシャライザとconvenience イニシャライザの構文(Syntax for Designated and Convenience Initializers)

クラスの指定イニシャライザは、値型のシンプルなイニシャライザと同じ方法で記述します:

init(<#parameters#>) {
   <#statements#>
}

convenience イニシャライザは同じスタイルで記述できますが、init キーワードの前にスペース区切りで convenience 修飾子を配置します:

convenience init(<#parameters#>) {
   <#statements#>
}

クラス型のイニシャライザの委譲(Initializer Delegation for Class Types)

指定イニシャライザと convenience イニシャライザ間の関係を簡潔にするために、Swift はイニシャライザ間の呼び出しの委譲に次の 3 つのルールを適用します:

  • ルール 1 指定イニシャライザは、直接スーパークラスの指定イニシャライザを呼び出す必要があります

  • ルール 2 convenience イニシャライザは、同じクラスの別のイニシャライザを呼び出す必要があります

  • ルール 3 convenience イニシャライザは、最終的に指定イニシャライザを呼び出す必要があります

これを覚える簡単な方法は次のとおりです。

  • 指定イニシャライザは常に上に委譲する必要があります

  • convenience イニシャライザは常に委譲する必要があります

これらのルールを次の図に示します:

ここでは、スーパークラスには、1 つの指定イニシャライザと 2 つの convenience イニシャライザがあります。ある convenience イニシャライザが別の convenience イニシャライザを呼び出し、それが次に単一の指定イニシャライザを呼び出します。これは、上記のルール 2 と 3 を満たします。スーパークラス自体にはそれ以上のスーパークラスがないため、ルール 1 は適用されません。

この図のサブクラスには、2 つの指定イニシャライザと 1 つの convenience イニシャライザがあります。convenience イニシャライザは、同じクラスから別のイニシャライザしか呼び出せないため、2 つの指定イニシャライザのいずれかを呼び出す必要があります。これは、上記のルール 2 と 3 を満たします。上記のルール 1 を満たすには、両方の指定イニシャライザがスーパークラスの単一の指定イニシャライザを呼び出す必要があります。

NOTE これらのルールは、クラスのユーザが各クラスのインスタンスを作成する方法には影響しません。上の図の全てのイニシャライザを使用して、それらが属するクラスの完全に初期化されたインスタンスを作成できます。ルールは、クラスのイニシャライザの実装の記述方法にのみ影響します。

次の図は、4 つのクラスのより複雑なクラス階層を示しています。これは、この階層の指定イニシャライザがクラスイニシャライザ初期化の「漏斗」のように機能し、チェーン内のクラス間の相互関係をシンプルにする方法を示しています:

2段階の初期化(Two-Phase Initialization)

Swift でのクラスの初期化は 2 段階のプロセスです。最初の段階では、各格納プロパティに、それを導入したクラスが初期値を割り当てます。全ての格納プロパティの初期状態が決定されると、第 2 段階が開始され、新しいインスタンスを使用する準備が整ったと見なされる前に、各クラスの格納プロパティをさらにカスタマイズする機会が与えられます。

2 段階の初期化プロセスを使用すると、初期化が安全になり、クラス階層内の各クラスに完全な柔軟性がもたらされます。2 段階の初期化は、プロパティ値が初期化される前にアクセスされるのを防ぎ、プロパティ値が別のイニシャライザによって予期せず別の値に設定されるのを防ぎます。

NOTE Swift の 2 段階の初期化プロセスは、Objective-C の初期化に似ています。主な違いは、第 1 段階では、Objective-C がゼロまたはヌル値 (0nil など) を全てのプロパティに割り当てることです。Swift の初期化フローは、カスタムの初期値を設定できるという点でより柔軟であり、0 または nil が有効なデフォルト値ではない型に対処できます。

Swift のコンパイラは、2 段階の初期化がエラーなしで完了したことを確認するために、4 つの有用な安全チェックを実行します。

安全チェック 1

指定イニシャライザは、スーパークラスのイニシャライザに委譲する前に、そのクラスによって導入された全てのプロパティが初期化されていることを確認する必要があります

前述のように、オブジェクトのメモリは、全ての格納プロパティの初期状態が判明すると、完全に初期化されたと見なされます。このルールが満たされるためには、指定イニシャライザは、チェーンを引き継ぐ前に、それ自体の全てのプロパティが初期化されていることを確認する必要があります。

安全チェック 2

指定イニシャライザは、継承したプロパティに値を割り当てる前に、スーパークラスのイニシャライザに委譲する必要があります。そうしないと、指定イニシャライザが割り当てる新しい値は、スーパークラスの初期化によって上書きされてしまいます

安全チェック 3

convenience イニシャライザは、値をプロパティ(同じクラスで定義されたプロパティを含む)に割り当てる前に、別のイニシャライザに委譲する必要があります。そうしないと、convenience イニシャライザが割り当てる新しい値は、独自クラスの指定イニシャライザによって上書きされます

安全チェック 4

イニシャライザは、初期化の最初の段階が完了するまで、インスタンスメソッドを呼び出したり、インスタンスプロパティの値を読み取ったり、値として自分自身を参照したりすることはできません

クラスインスタンスは、最初の段階が終了するまで完全には有効ではありません。プロパティへのアクセスとメソッドの呼び出しは、最初の段階の終了時にクラスインスタンスが有効なことがわかった場合にのみ可能です。

上記の 4 つの安全チェックに基づいて、2 段階の初期化がどのように実行されるかを次に示します:

第 1 段階

  • 指定イニシャライザまたは convenience イニシャライザがクラスで呼び出されます

  • そのクラスの新しいインスタンスがメモリに割り当てられます。メモリはまだ初期化されていません

  • そのクラスの指定イニシャライザは、そのクラスによって導入された全ての格納プロパティに値があることを確認します。この時点でこれらの格納プロパティのメモリが初期化されました

  • 指定イニシャライザは、スーパークラスがそれ自身の格納プロパティに対して同じタスクを実行するために、スーパークラスのイニシャライザに委譲します

  • これは、初期化チェーンの最上位に到達するまで、クラスの継承チェーンが継続します

  • チェーンのトップに到達し、チェーン内の最後のクラスが、全ての格納プロパティに値があることを確認すると、インスタンスのメモリは完全に初期化されたと見なされ、第 1 段階が完了します

第 2 段階

  • チェーンの最上位から下に戻ると、チェーン内の指定された各イニシャライザは、インスタンスをさらにカスタマイズすることができます。イニシャライザは、self にアクセスして、そのプロパティを変更したり、インスタンスメソッドを呼び出したりできるようになりました

  • 最後に、チェーン内の convenience イニシャライザは、インスタンスをカスタマイズして self を操作することができるようになります

仮のサブクラスとスーパークラスを使った第 1 段階の初期化呼び出し方法は次のとおりです:

この例では、初期化はサブクラスの convenience イニシャライザの呼び出しから始まります。この convenience イニシャライザは、まだプロパティを変更できません。まず convenience イニシャライザから同じクラスの指定イニシャライザに委譲します。

指定イニシャライザは、安全チェック 1 に従って、サブクラスの全てのプロパティに値があることを確認します。次に、指定イニシャライザからそのスーパークラスを呼び出して、チェーンの上に向かって初期化を続行します。

スーパークラスの指定イニシャライザは、スーパークラスの全てのプロパティに値があることを確認します。初期化するスーパークラスはこれ以上ないため、これ以上の委譲は必要ありません。

スーパークラスの全てのプロパティに初期値が設定されるとすぐに、そのメモリは完全に初期化されたと見なされ、第 1 段階が完了します。

第 2 段階での同じ初期化呼び出し方法は次のとおりです:

スーパークラスの指定イニシャライザは、インスタンスをさらにカスタマイズする機会を得ます(ただし、必ず必要ではありません)。

スーパークラスの指定イニシャライザが完了すると、サブクラスの指定イニシャライザで追加のカスタマイズを実行できます(ただし、これも必ずしも行う必要はありません)。

最後に、サブクラスの指定イニシャライザが終了すると、最初に呼び出された convenience イニシャライザは追加のカスタマイズを実行できます。

イニシャライザの継承とオーバーライド(Initializer Inheritance and Overriding)

Objective-C のサブクラスとは異なり、Swift のサブクラスはデフォルトでスーパークラスのイニシャライザを継承しません。Swift のアプローチは、スーパークラスからのシンプルなイニシャライザが、より具体的なサブクラスによって継承され、完全または正しく初期化されていないサブクラスの新しいインスタンスを作成すること防ぎます。

NOTE スーパークラスのイニシャライザは特定の状況で継承できますが、安全で適切な場合に限ります。詳細については、以下のAutomatic Initializer Inheritance(自動イニシャライザの継承)を参照してください。

サブクラスでそのスーパークラスの 1 つ以上の同じイニシャライザを使用したい場合、サブクラス内でそれらのイニシャライザをカスタムした実装を提供できます。

スーパークラスの指定イニシャライザに一致するサブクラスのイニシャライザを作成すると、その指定イニシャライザのオーバーライドを効率的に提供することができます。これを行うためには、サブクラスのイニシャライザ定義の前に override 修飾子を記述する必要があります。Default Initializers(デフォルトイニシャライザ)で説明されているように、これは、自動的に提供されるデフォルトのイニシャライザをオーバーライドする場合にも当てはまります。

オーバーライドされたプロパティ、メソッド、またはサブスクリプトの場合と同様に、override 修飾子が存在すると、Swift は、スーパークラスにオーバーライドされる指定イニシャライザがあるかどうかを確認し、オーバーライドするイニシャライザのパラメータが意図したとおりの形になっていることを検証します。

NOTE サブクラスのイニシャライザの実装が convenience イニシャライザであっても、スーパークラスで指定イニシャライザをオーバーライドするときは、常に override 修飾子を記述します。

逆に、スーパークラスの convenience イニシャライザと一致するサブクラスのイニシャライザを作成する場合、上記のInitializer Delegation for Class Types(クラス型のイニシャライザの委譲)で説明したルールに従って、そのスーパークラスの convenience イニシャライザがサブクラスから直接呼び出されることはありません。したがって、サブクラスは(厳密に言えば)スーパークラスのイニシャライザをオーバーライドしていません。その結果、スーパークラスの convenience イニシャライザに一致する実装を提供する場合は、override 修飾子を記述しません。

下記の例では、Vehicle という基本クラスを定義しています。この基本クラスは、デフォルトの Int 値が 0 の numberOfWheels という格納プロパティを宣言しています。numberOfWheels プロパティは、description という計算プロパティで使用され、乗り物の特性を説明しています:

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) 輪車"
    }
}

Vehicle クラスは、その唯一の格納プロパティにデフォルト値を提供し、カスタムのイニシャライザを提供していません。その結果、Default Initializers(デフォルトイニシャライザ)で説明されているように、デフォルトのイニシャライザを自動的に受け取ります。デフォルトイニシャライザは、(使用可能な場合)常にクラスの指定イニシャライザで、numberOfWheels0 の新しい Vehicle インスタンスを作成するために使用できます:

let vehicle = Vehicle()
print("乗り物: \(vehicle.description)")
// 乗り物: 0 輪車

次の例では、Bicycle という Vehicle のサブクラスを定義しています:

class Bicycle: Vehicle {
    override init() {
        super.init()
        numberOfWheels = 2
    }
}

Bicycle サブクラスは、カスタムの指定イニシャライザ init() を定義します。この指定イニシャライザは Bicycle のスーパークラスの指定イニシャライザと一致するため、Bicycle では override 修飾子でマークされています。

Bicycleinit() イニシャライザは、最初に super.init() を呼び出します。これは、Bicycle クラスのスーパークラスの Vehicle のデフォルトのイニシャライザを呼び出します。これにより、Bicycle がプロパティを変更する前に、継承したプロパティ numberOfWheelsVehicle によって初期化されます。super.init() を呼び出した後、numberOfWheels の値は新しい値 2 に置き換えられます。

Bicycle のインスタンスを作成する場合、継承した description 計算プロパティを呼び出して、numberOfWheels プロパティがどのように更新されたかを確認できます:

let bicycle = Bicycle()
print("自転車: \(bicycle.description)")
// 自転車: 2 輪車

サブクラスのイニシャライザが第 2 段階の初期化プロセスで何も行わず、スーパークラスに引数のない指定イニシャライザがある場合、サブクラスの全ての格納プロパティに値を割り当てた後、super.init() の呼び出しを省略できます。

この例では、Hoverboard と呼ばれる Vehicle の別のサブクラスを定義しています。イニシャライザでは、Hoverboard クラスはその color プロパティのみを設定します。このイニシャライザは、super.init() を明示的に呼び出す代わりに、スーパークラスのイニシャライザを暗黙的に呼び出してプロセスを完了しています。

class Hoverboard: Vehicle {
    var color: String
    init(color: String) {
        self.color = color
        // super.init() は暗黙的に呼ばれています
    }
    override var description: String {
        return "キレイな \(color)\(super.description)"
    }
}

Hoverboard のインスタンスは、Vehicle イニシャライザによって提供されるデフォルトのホイール数を使用します。

let hoverboard = Hoverboard(color: "silver")
print("ホバーボード: \(hoverboard.description)")
// ホバーボード: キレイな silver の 0 車輪

NOTE サブクラスは、初期化中に継承した変数プロパティを変更できますが、継承した定数プロパティを変更することはできません。

自動イニシャライザの継承(Automatic Initializer Inheritance)

上記のように、サブクラスはデフォルトでスーパークラスのイニシャライザを継承しません。ただし、特定の条件が満たされた場合、スーパークラスのイニシャライザは自動的に継承されます。つまり、実際には、多くの場合でイニシャライザのオーバーライドを記述する必要がなく、安全にオーバーライドできる場合はいつでも最小限の手間で、スーパークラスのイニシャライザを継承できます。

サブクラスに導入する新しいプロパティにデフォルト値を提供する場合、次の 2 つのルールが適用されます:

ルール 1

  • サブクラスが指定イニシャライザを定義していない場合、サブクラスはそのスーパークラスの指定イニシャライザを全て自動的に継承します

ルール 2

  • サブクラスが、ルール 1 に従ってスーパークラスの指定イニシャライザを自動で継承するか、またはカスタムのイニシャライザを実装することによってスーパークラスの全ての指定イニシャライザを実装する場合、全てのスーパークラスの convenience イニシャライザを自動的に継承します

これらのルールは、サブクラスがさらに convenience イニシャライザを追加した場合にも適用されます。

NOTE サブクラスは、ルール 2 を満たす方法の 1 つとして、スーパークラスの指定イニシャライザをサブクラスの convenience イニシャライザとして実装できます。

指定とconvenience イニシャライザの挙動(Designated and Convenience Initializers in Action)

次の例は、指定イニシャライザ、convenience イニシャライザ、および自動イニシャライザの継承の挙動を示しています。この例では、FoodRecipeIngredient、および ShoppingListItem という 3 つのクラスの階層を定義し、それらのイニシャライザがどのように相互作用するかを示しています。

階層の基本クラスは Food と呼ばれ、食品の名前をカプセル化するシンプルなクラスです。Food クラスは、name という単一の String プロパティを宣言し、Food インスタンスを作成するために 2 つのイニシャライザを提供します。

class Food {
    var name: String
    init(name: String) {
        self.name = name
    }
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

次の図は、Food クラスのイニシャライザのチェーンを示しています。

クラスにはデフォルトで_メンバワイズイニシャライザ_を生成しないため、Food クラスは、name という単一の引数を受け取る指定イニシャライザを提供します。このイニシャライザを使用して、特定の名前で新しい Food インスタンスを作成できます。

let namedMeat = Food(name: "Bacon")
// namedMeat の name は "Bacon"

Food クラスの init(name: String) イニシャライザは、指定イニシャライザとして提供されています。これは、新しい Food インスタンスの全ての格納プロパティが完全に初期化されていることを保証するためです。Food クラスにはスーパークラスがないため、init(name: String) イニシャライザは、初期化を完了するために super.init() を呼び出す必要はありません。

Food クラスは、引数のない convenience イニシャライザ init() も提供します。init() イニシャライザは、[Unnamed] のという name の値を使って Food クラスの init(name: String) に委譲することで、新しい食品にデフォルトのプレースホルダ名を提供します。

let mysteryMeat = Food()
// mysteryMeat の name は "[Unnamed]"

階層の 2 番目のクラスは、RecipeIngredient と呼ばれる Food のサブクラスです。RecipeIngredient クラスは、料理レシピの材料をモデル化しています。これは、(Food から継承する name プロパティに加えて) amount という Int プロパティを宣言し、RecipeIngredient インスタンスを作成するための 2 つのイニシャライザを提供します。

class RecipeIngredient: Food {
    var quantity: Int
    init(name: String, quantity: Int) {
        self.quantity = quantity
        super.init(name: name)
    }
    override convenience init(name: String) {
        self.init(name: name, quantity: 1)
    }
}

次の図は、RecipeIngredient クラスのイニシャライザのチェーンを示しています。

RecipeIngredient クラスには、単一の指定イニシャライザ init(name: String, amount: Int) があり、これを使用して新しい RecipeIngredient インスタンスの全てのプロパティを設定できます。このイニシャライザは、渡された quantity 引数を quantity プロパティに割り当てることから開始します。これは、RecipeIngredient で定義された唯一の新しいプロパティです。その後、イニシャライザは Food クラスの init(name: String) イニシャライザに委譲します。このプロセスは、上記のTwo-Phase Initialization(2 段階の初期化)の初期化の安全チェック 1 を満たしています。

RecipeIngredient は、名前だけで RecipeIngredient インスタンスを作成する convenience イニシャライザ init(name: String) も定義します。この convenience イニシャライザは、数量を 1 と想定して、明示的に数量を指定せずに RecipeIngredient インスタンスを作成します。この convenience イニシャライザの定義により、RecipeIngredient インスタンスの作成がより迅速かつ便利になり、単一量の RecipeIngredient インスタンスを複数作成する際にコードの重複が回避できます。この convenience イニシャライザは、単にクラスの指定イニシャライザに委譲し、quantity1 を渡します。

RecipeIngredient によって提供される init(name: String) convenience イニシャライザは、Foodinit(name: String) 指定イニシャライザと同じパラメータを受け取ります。つまり、この convenience イニシャライザはスーパークラスから指定イニシャライザをオーバーライドするため、override 修飾子を付ける必要があります(Initializer Inheritance and Overriding(イニシャライザの継承とオーバーライド)を参照)。

RecipeIngredient は、convenience イニシャライザとして init(name: String) イニシャライザを提供していますが、さらにそのスーパークラスの指定イニシャライザの全ての実装を提供しています。したがって、RecipeIngredient は、そのスーパークラスの convenience イニシャライザも全て自動的に継承します。

この例では、RecipeIngredient のスーパークラスは Food で、init() という名前の convenience イニシャライザが 1 つあります。したがって、このイニシャライザは RecipeIngredient に継承されます。継承されたバージョンの init() は、Food バージョンではなく RecipeIngredient バージョンの init(name:String) に委譲することを除いて、Food バージョンとまったく同じように機能します。

これらの 3 つの全てのイニシャライザを使用して、新しい RecipeIngredient インスタンスを作成できます:

let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)

階層の 3 番目の最後のクラスは、ShoppingListItem と呼ばれる RecipeIngredient のサブクラスです。ShoppingListItem クラスは、ショッピングリストに表示されるレシピの材料をモデル化しています。

ショッピングリストの全てのアイテムは、「未購入」として始まります。これを表すために、ShoppingListItem には、purchased という Bool 型プロパティが導入されており、デフォルト値は false です。ShoppingListItem は、インスタンスの説明をする description という計算プロパティも追加しています。

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? " ✔" : " ✘"
        return output
    }
}

NOTE ShoppingListItem は、purchased の初期値を提供するイニシャライザを定義しません。これは、(ここでモデル化されている)ショッピングリスト内のアイテムは常に未購入から始まるためです。

導入する全てのプロパティにデフォルト値を提供し、それ自体はイニシャライザを定義しないため、ShoppingListItem は全ての指定イニシャライザと convenience イニシャライザをスーパークラスから自動的に継承します。

次の図は、3 つのクラス全てのイニシャライザのチェーンの全体を示しています。

継承した 3 つのイニシャライザ全てを使用して、新しい ShoppingListItem インスタンスを作成できます。

var breakfastList = [
    ShoppingListItem(),
    ShoppingListItem(name: "Bacon"),
    ShoppingListItem(name: "Eggs", quantity: 6),
]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
    print(item.description)
}
// 1 x Orange juice ✔
// 1 x Bacon ✘
// 6 x Eggs ✘

ここでは、3 つの新しい ShoppingListItem インスタンスを含む配列リテラルから、breakfastList という新しい配列が作成されます。配列の型は [ShoppingListItem] だと推論されます。配列が作成された後、配列の先頭にある ShoppingListItem の名前が "[Unnamed]" から "Orange juice" に変更され、購入済みとしてマークされます。配列内の各アイテムの説明を出力すると、デフォルトの状態が期待どおりに設定されていることがわかります。

失敗可能イニシャライザ(Failable Initializers)

初期化が失敗する可能性があるクラス、構造体、または列挙型を定義すると便利な場合があります。この失敗は、無効な初期化パラメータ値、必要な外部リソースの欠如、または初期化の成功を妨げるその他の条件によって引き起こされる可能性があります。

失敗する可能性のある初期化条件に対処するには、クラス、構造体、または列挙型定義の一部として、1 つ以上の_失敗可能イニシャライザ_を定義します。init キーワードの後に疑問符を置いて、失敗するイニシャライザを記述します(init?)。

NOTE 同じパラメータの型と名前で失敗可能イニシャライザと失敗しないイニシャライザを定義することはできません。

失敗可能なイニシャライザは、初期化する型のオプショナルの値を作成します。初期化に失敗する場所を示すために、失敗可能イニシャライザ内に return nil を記述します。

NOTE 厳密に言えば、イニシャライザは値を返しません。むしろ、その役割は、初期化が終了するまでに自分自身が完全かつ正しく初期化されることを保証することです。初期化に失敗した場合 return nil を記述しますが、初期化の成功を示すために return キーワードを使用することはありません。

例えば、失敗可能イニシャライザは、数値型変換のために実装されます。数値型間の変換で値が正確に維持されるようにするには、init(exactly:) イニシャライザを使用します。型変換で値を維持できない場合、イニシャライザは失敗します:

let wholeNumber: Double = 12345.0
let pi = 3.14159

if let valueMaintained = Int(exactly: wholeNumber) {
    print("\(wholeNumber) を Int に変換すると \(valueMaintained) になります") 
}
// 12345.0 を Int に変換すると 12345 になります

let valueChanged = Int(exactly: pi)
// valueChanged は Int? で Int ではありません

if valueChanged == nil {
    print("\(pi) を Int に変換することはできません")
}
// 3.14159 を Int に変換することはできません

下記の例では、Animal と呼ばれる構造体を定義し、species と呼ばれる String の定数プロパティを使用しています。Animal 構造体は、species と呼ばれる単一のパラメータを持つ失敗可能イニシャライザも定義します。このイニシャライザは、イニシャライザに渡された species の値が空の文字列かどうかをチェックします。空の文字列が見つかった場合、初期化に失敗します。それ以外の場合、species のプロパティの値が設定され、初期化が成功します:

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}

この失敗可能イニシャライザを使用して、新しい Animal インスタンスの初期化を試み、初期化が成功したかどうかを確認できます:

let someCreature = Animal(species: "Giraffe")
// someCreature は Animal? で Animal ではありません

if let giraffe = someCreature {
    print("動物の種類は \(giraffe.species) として初期化されました")
}
// 動物の種類は Giraffe として初期化されました

空の文字列を失敗可能イニシャライザの species パラメータに渡すと、イニシャライザは初期化に失敗します:

let anonymousCreature = Animal(species: "")
// anonymousCreature は Animal? で Animal ではありません

if anonymousCreature == nil {
    print("この未知の生き物は初期化ができませんでした")
}
// この未知の生き物は初期化ができませんでした

NOTE 空の文字列 ("Giraffe" ではなく "" など) のチェックは、オプショナルの String 値がないことを示す nil のチェックとは異なります。上記の例では、空の文字列 ("") は妥当な非 オプショナルの文字列です。ただし、Animal がその species プロパティの値として空の文字列を持つことは適切ではありません。この制限をモデル化するために、空の文字列が見つかった場合、失敗可能イニシャライザは初期化に失敗します。

列挙型の失敗可能イニシャライザ(Failable Initializers for Enumerations)

失敗可能イニシャライザを使用して、1 つ以上のパラメータに基づいて適切な列挙ケースを選択できます。提供されたパラメータが適切な列挙ケースと一致しない場合、イニシャライザは失敗する可能性があります。

以下の例では、3 つの可能な状態(kelvincelsiusfahrenheit)を使用して、TemperatureUnit という列挙型を定義しています。温度記号を表す Character 値の適切な列挙ケースを見つけるために、失敗可能イニシャライザが使用されます:

enum TemperatureUnit {
    case kelvin, celsius, fahrenheit
    init?(symbol: Character) {
        switch symbol {
        case "K":
            self = .kelvin
        case "C":
            self = .celsius
        case "F":
            self = .fahrenheit
        default:
            return nil
        }
    }
}

この失敗可能イニシャライザを使用して、3 つの可能な状態に対して適切な列挙ケースを選択し、パラメータがこれらの状態のいずれかに一致しない場合に初期化を失敗する可能性があります。

let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
    print("これは温度の単位として定義されているので、初期化に成功しました。")
}
// これは温度の単位として定義されているので、初期化に成功しました。

let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
    print("これは温度の単位としては定義されていないので、初期化に失敗しました。")
}
// これは温度の単位としては定義されていないので、初期化に失敗しました。

Raw Value を持つ列挙型の失敗可能イニシャライザ(Failable Initializers for Enumerations with Raw Values)

Raw Value を持つ列挙型は、適切な Raw Value 型の rawValue と呼ばれるパラメータを受け取り、一致する列挙ケースが見つかった場合はそれを選択するか、一致する値がない場合は初期化に失敗する失敗可能イニシャライザ init?(rawValue:) を自動的に生成します。

上記の TemperatureUnit の例を書き直して、Character 型の Raw Value を使用し、init?(rawValue:) イニシャライザを利用できます:

enum TemperatureUnit: Character {
    case kelvin = "K", celsius = "C", fahrenheit = "F"
}

let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
    print("これは温度の単位として定義されているので、初期化に成功しました。")
}
// これは温度の単位として定義されているので、初期化に成功しました。

let unknownUnit = TemperatureUnit(rawValue: "X")
if unknownUnit == nil {
    print("これは温度の単位としては定義されていないので、初期化に失敗しました。")
}
// これは温度の単位としては定義されていないので、初期化に失敗しました。

初期化の失敗の伝播(Propagation of Initialization Failure)

クラス、構造体、または列挙型の失敗可能イニシャライザは、同じクラス、構造体、または列挙型から別の失敗可能イニシャライザに委譲できます。同様に、サブクラスの失敗可能イニシャライザは、スーパークラスの失敗可能イニシャライザに委譲できます。

いずれの場合も、委譲した別のイニシャライザで初期化に失敗すると、初期化プロセス全体がすぐに失敗し、それ以上の初期化コードは実行されません。

NOTE 失敗可能イニシャライザから失敗しないイニシャライザに委譲することもできます。他の方法では失敗しない既存の初期化プロセスに失敗する可能性を追加する必要がある場合は、このアプローチを使用します。

下記の例では、CartItem という Product のサブクラスを定義しています。CartItem クラスは、オンラインショッピングカート内のアイテムをモデル化しています。CartItem は、quantity と呼ばれる定数格納プロパティを導入し、このプロパティが常に少なくとも 1 の値を持つようにします:

class Product {
    let name: String
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

class CartItem: Product {
    let quantity: Int
    init?(name: String, quantity: Int) {
        if quantity < 1 { return nil }
        self.quantity = quantity
        super.init(name: name)
    }
}

CartItem の失敗可能イニシャライザは、1 以上の quantity の値を受け取ったかどうかを検証することから始まります。quantity が無効な場合、初期化プロセス全体はすぐに失敗し、それ以上の初期化コードは実行されません。同様に、Product の失敗可能イニシャライザは name の値をチェックし、name が空の文字列の場合、初期化プロセスはすぐに失敗します。

name が空でなく、quantity1 以上の CartItem インスタンスを作成すると、初期化は成功します:

if let twoSocks = CartItem(name: "sock", quantity: 2) {
    print("商品: \(twoSocks.name), 在庫数: \(twoSocks.quantity)")
}
// 商品: sock, 在庫数: 2

quantity の値が 0CartItem インスタンスを作成しようとすると、CartItem のイニシャライザによって初期化が失敗します:

if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
    print("商品: \(zeroShirts.name), 在庫数: \(zeroShirts.quantity)")
} else {
    print("0 個のワイシャツは初期化できません")
}
// 0 個のワイシャツは初期化できません

同様に、name の値が空の CartItem インスタンスを作成しようとすると、スーパークラスの Product イニシャライザによって初期化が失敗します。

if let oneUnnamed = CartItem(name: "", quantity: 1) {
    print("商品: \(oneUnnamed.name), 在庫数: \(oneUnnamed.quantity)")
} else {
    print("名前のない商品は初期化できません")
}
// 名前のない商品は初期化できません

失敗可能イニシャライザのオーバーライド(Overriding a Failable Initializer)

他のイニシャライザと同様に、サブクラスでスーパークラスの失敗可能イニシャライザをオーバーライドできます。または、スーパークラスの失敗可能イニシャライザをサブクラスの失敗しないイニシャライザでオーバーライドできます。これにより、スーパークラスの初期化が失敗しても、初期化が失敗しないサブクラスを定義できます。

スーパークラスの失敗可能イニシャライザをサブクラスの失敗しないイニシャライザでオーバーライドする場合、スーパークラスのイニシャライザに委譲する唯一の方法は、スーパークラスの失敗可能イニシャライザの結果を強制アンラップすることです。

NOTE 失敗可能イニシャライザを失敗しないイニシャライザでオーバーライドすることはできますが、その逆はできません。

下記の例では、Document というクラスを定義しています。このクラスは、空ではない文字列値または nil のいずれかが可能ですが、空文字列にすることはできない name プロパティで初期化できるドキュメントをモデル化しています。

class Document {
    var name: String?
    // このイニシャライザは name が nil の Document を作成します
    init() {}
    // このイニシャライザは name が 空文字ではない Document を作成します
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

次の例では、AutomaticallyNamedDocument という Document のサブクラスを定義しています。AutomaticNamedDocument サブクラスは、Document の指定イニシャライザの両方をオーバーライドします。これらのオーバーライドにより、インスタンスが名前なしで初期化された場合、または空の文字列が init(name:) イニシャライザに渡された場合に、AutomaticallyNamedDocument インスタンスは "[Untitled]"name の初期値として持つことが保証されます。

class AutomaticallyNamedDocument: Document {
    override init() {
        super.init()
        self.name = "[Untitled]"
    }
    override init(name: String) {
        super.init()
        if name.isEmpty {
            self.name = "[Untitled]"
        } else {
            self.name = name
        }
    }
}

AutomaticNamedDocument は、スーパークラスの失敗可能イニシャライザ(init?(name:))を、失敗しないイニシャライザ(init(name:))でオーバーライドします。AutomaticNamedDocument は、スーパークラスとは異なる方法で空の文字列に対処するため、イニシャライザは失敗することがなく、代わりに失敗しないバージョンのイニシャライザを提供します。

イニシャライザで強制アンラップを使用して、サブクラスの失敗しないイニシャライザの実装の一部として、スーパークラスから失敗可能イニシャライザを呼び出すことができます。例えば、以下の UntitledDocument サブクラスは常に "[Untitled]" という名前で、初期化中にスーパークラスの失敗可能イニシャライザ(init(name:))を使用します:

class UntitledDocument: Document {
    override init() {
        super.init(name: "[Untitled]")!
    }
}

この場合、スーパークラスの init(name:) イニシャライザが名前に空の文字列を指定して呼び出された場合、強制アンラップにより実行時エラーが発生しますが、文字列定数で呼び出されているため、イニシャライザが失敗しないことがわかり、この場合は実行時エラーは発生しません。

init!を使った失敗可能イニシャライザ(The init! Failable Initializer)

通常、init キーワードの後に疑問符(?)を配置して(init?)、適切な型のオプショナルのインスタンスを作成する失敗可能イニシャライザを定義します。または、暗黙アンラップオプショナル型インスタンスを作成する失敗可能イニシャライザを定義することもできます。これを行うには、疑問符の代わりに init キーワードの後に感嘆符(!)を置きます(init!)。

init? から init! へ委譲でき、その逆も可能です。また、init!init? をオーバーライドでき、その逆も可能です。init から init! に委譲することもできますが、init! の初期化が失敗した場合は、アサーションを引き起こします。

必須イニシャライザ(Required Initializers)

クラスの全てのサブクラスがそのイニシャライザを実装する必要があることを示すために、クラスのイニシャライザの定義の前に required 修飾子を記述します。

class SomeClass {
    required init() {
        // ここにイニシャライザの実装を書きます
    }
}

また、全てのサブクラスの必須イニシャライザの実装の前に、required 修飾子を記述して、イニシャライザの必須要件が初期化チェーン内の他のサブクラスに適用されることを示す必要があります。必須指定イニシャライザをオーバーライドするときは、override 修飾子を記述しません:

class SomeSubclass: SomeClass {
    required init() {
        // ここにサブクラスの必須イニシャライザを実装します
    }
}

NOTE 継承したイニシャライザで要件を満たすことができる場合、必須イニシャライザの明示的な実装を提供する必要はありません。

クロージャまたは関数を使用したプロパティのデフォルト値の設定(Setting a Default Property Value with a Closure or Function)

格納プロパティのデフォルト値にカスタマイズまたは設定が必要な場合は、クロージャまたはグローバル関数を使用して、そのプロパティにカスタマイズされたデフォルト値を提供できます。プロパティが属する型の新しいインスタンスが初期化されるたびに、クロージャまたは関数が呼び出され、その戻り値がプロパティのデフォルト値として割り当てられます。

これらの種類のクロージャまたは関数は、通常、プロパティと同じ型の一時的な値を作成し、その値を調整して目的の初期状態にしてからその値を返し、プロパティのデフォルト値として使用します。

クロージャを使用してプロパティのデフォルト値を提供する方法の概要を次に示します:

class SomeClass {
    let someProperty: SomeType = {
        // このクロージャ内に someProperty のデフォルト値を作成します
        // someValue は SomeType と同じ型である必要があります
        return someValue
    }()
}

クロージャの終了中括弧(})の後に空の括弧(())が続いていることに注目してください。これは Swift にクロージャをすぐに実行するように指示します。これらの括弧を省略すると、クロージャの戻り値ではなく、クロージャ自体をプロパティに割り当てようとします。

NOTE クロージャを使用してプロパティを初期化する場合、残りのインスタンスはクロージャが実行された時点ではまだ初期化されていないことに注意してください。つまり、たとえプロパティにデフォルト値があっても、クロージャ内から他のプロパティ値にアクセスできないことを意味します。また、暗黙的な self プロパティを使用したり、インスタンスのメソッドを呼び出すこともできません。

下記の例では、チェスゲームのボードをモデル化する Chessboard という構造体を定義しています。チェスは 8 x 8 の盤上で、白と黒の正方形が交互に並んでいます。

このゲームボードを表すために、Chessboard 構造体には、boardColors という 1 つのプロパティがあり、これは 64 の Bool 値の配列です。配列内の true の値は黒い正方形を表し、false の値は白い正方形を表します。配列の最初のアイテムはボードの左上の正方形を表し、配列の最後のアイテムはボードの右下の正方形を表します。

boardColors 配列は、色の値を設定するためのクロージャで初期化されています。

struct Chessboard {
    let boardColors: [Bool] = {
        var temporaryBoard: [Bool] = []
        var isBlack = false
        for i in 1...8 {
            for j in 1...8 {
                temporaryBoard.append(isBlack)
                isBlack = !isBlack
            }
            isBlack = !isBlack
        }
        return temporaryBoard
    }()
    func squareIsBlackAt(row: Int, column: Int) -> Bool {
        return boardColors[(row * 8) + column]
    }
}

新しい Chessboard インスタンスが作成されるたびに、クロージャが実行され、boardColors のデフォルト値を計算して返します。上記の例のクロージャは、ボード上の各マスの適切な色を計算して、temporaryBoard と呼ばれる一時配列に設定し、セットアップが完了すると、この一時配列をクロージャの戻り値として返します。返された配列値は boardColors に保存され、squareIsBlackAt(row:column:) というユーティリティ関数から検索できます。

let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// true
print(board.squareIsBlackAt(row: 7, column: 7))
// false

最終更新