初期化

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

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

クラス型のインスタンスは、そのクラスのインスタンスが割り当て解除される直前にカスタムクリーンアップを実行するdeinitializerも実装できます。デイニシャライザーの詳細については、「デイニシャライゼーション」を参照してください。

保存されたプロパティの初期値の設定

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

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

注意

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

イニシャライザー

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

  1. init() {
  2. // perform some initialization here
  3. }

以下の例では、華氏Fahrenheitのスケールで表された温度を保存するために呼び出される新しい構造を定義しています。Fahrenheitは、1つの格納されているDouble型のプロパティtemperatureを有します。

struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// Prints "The default temperature is 32.0° Fahrenheit"

構造体は、パラメーターのない単一のイニシャライザー init を定義します。これにより、保存されている温度が32.0(華氏での水の氷点)の値で初期化されます。

デフォルトのプロパティ値

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

注意

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

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

struct Fahrenheit {
    var temperature = 32.0
}

初期化のカスタマイズ

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

初期化パラメーター

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

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

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 is 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius is 0.0

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

パラメータ名と引数ラベル

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

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

次の例では、Colorという、3つの定数プロパティredgreen、およびblueを有する構造体を定義します。これらのプロパティは、色の赤、緑、青の量を示す0.0〜1.0の間の値を格納します。

Colorは、Double型の赤、緑、青のコンポーネントのタイプの3つの適切に名前が付けられたパラメーターを持つイニシャライザーを提供します。Colorまた、Double型のwhiteパラメーターを持つイニシャライザを提供します。これは、3つのカラーコンポーネントすべてに同じ値を提供するために使用されます。

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)

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

  1. let veryGreen = Color(0.0, 1.0, 0.0)
  2. // 引数ラベルが指定されていないため、コンパイル時エラー

引数ラベルのない初期化パラメーター

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

上記の初期化パラメーター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 is 37.0

イニシャライザの呼び出しCelsius(37.0)は、その意図が明確で、引数ラベルを必要としません。したがって、名前のない値を指定して呼び出すことができるように、このイニシャライザを作成するのが適切です。

オプショナルプロパティタイプ

カスタムタイプに「値なし」を論理的に許可する格納するプロパティがある場合(おそらく初期化中にその値を設定できないため、または後で「値なし」を許可されているため)、オプションタイプのプロパティを宣言します。オプショナルタイプのプロパティは、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: "Do you like cheese?")
cheeseQuestion.ask()
// Prints "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."

アンケートの質問に対する回答は、質問されるまで知ることができないため、responseプロパティはString?のタイプ、または「optional String」で宣言されます。新しいインスタンスSurveyQuestionが初期化されると、「文字列がまだない」という意味のデフォルト値 nil が自動的に割り当てられます。

初期化中に定数プロパティを割り当てる

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

注意

クラスインスタンスの場合、定数プロパティは、それを導入するクラスによってのみ初期化中に変更できます。サブクラスで変更することはできません。

上記の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: "How about beets?")
beetsQuestion.ask()
// Prints "How about beets?"
beetsQuestion.response = "I also like beets. (But not with cheese.)"

デフォルトのイニシャライザ

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

この例では、ShoppingListItemと呼ばれるクラスを定義しています。これは、買い物リスト内の商品の名前、数量、購入状況をカプセル化します。

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

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

struct 型のメンバーワイズイニシャライザ

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

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

次の例では、width および heightという2つのプロパティで呼び出される Size 構造体を定義しています。どちらのプロパティも、デフォルト値0.0を割り当てることでDouble 型が推定されます。

Size構造体は自動的にinit(width:height:) のメンバーワイズイニシャライザ受け取り、新しいSizeインスタンスを初期化するために使用することができます:

The Size structure automatically receives an init(width:height:) memberwise initializer, which you can use to initialize a new Size instance:

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)
// Prints "0.0 2.0"

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

値型のイニシャライザデリゲーション

イニシャライザは他のイニシャライザを呼び出して、インスタンスの初期化の一部を実行できます。イニシャライザデリゲーションと呼ばれるこのプロセスは、複数のイニシャライザ間でのコードの重複を回避します。

イニシャライザデリゲーションのしくみ、および許可されるデリゲーションの形式の規則は、値型とクラス型で異なります。値型(構造体および列挙型)は継承をサポートしていません。したがって、提供する別のイニシャライザにしか委任できないため、イニシャライザのデリゲーションプロセスは比較的単純です。記載されたクラスは、しかし、他のクラスから継承することができます。つまり、クラスには、初期化時に継承されたすべての格納されたプロパティに適切な値が割り当てられるようにするための追加の責任があります。これらの責任は、以下のクラスの継承と初期化で説明されています。

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

値型のカスタムイニシャライザを定義すると、その型のデフォルトのイニシャライザ(または構造体の場合はメンバーごとのイニシャライザ)にアクセスできなくなることに注意してください。この制約により、より複雑なイニシャライザで提供される追加の重要なセットアップが、自動イニシャライザの1つを使用している誰かによって誤って迂回される状況を防ぎます。

注意

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

次の例では、幾何学的な長方形を表すカスタム構造 Rect を定義しています。この例では、SizeおよびPointと呼ばれる2つのサポート構造が必要です。どちらも、すべてのプロパティのデフォルト値0.0を提供します。

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

以下の3つの方法のいずれかで Rect 構造体を初期化できます。デフォルトのゼロ初期化値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()は、構造が独自のカスタムイニシャライザを持たない場合に構造体が受け取るデフォルトのイニシャライザと機能的に同じです。このイニシャライザのボディは空で、中括弧の空のペア{}で表されます。このイニシャライザを呼び出すとRectが返され、そのインスタンスのoriginsizeプロパティの両方のデフォルト値がPoint(x: 0.0, y: 0.0) と Size(width: 0.0, height: 0.0)で初期化される。

let basicRect = Rect()
// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)

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

let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
                      size: Size(width: 5.0, height: 5.0))
// originRect's origin is (2.0, 2.0) and its size is (5.0, 5.0)

3番目のRectイニシャライザinit(center:size:)は少し複雑です。まず、centerポイントとsize値に基づいて適切な原点を計算します。次に、init(origin:size:)イニシャライザを呼び出し(またはデリゲート)、新しいイニシャライザとサイズの値を適切なプロパティに格納します。

let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
                      size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)

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

注意

init()init(origin:size:) イニシャライザを自分で定義せずにこの例を書く別の方法については、拡張機能を参照してください。

クラスの継承と初期化

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

Swiftは、保存されたすべてのプロパティが確実に初期値を受け取るようにするために、クラスタイプの2種類のイニシャライザを定義しています。これらは、指定イニシャライザおよび簡易イニシャライザと呼ばれています。

指定イニシャライザとコンビニエンスイニシャライザ

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

クラスには指定イニシャライザがほとんどない傾向があり、クラスが1つしか持たないことはよくあります。指定イニシャライザは、初期化が行われる「目標到達プロセス」ポイントであり、初期化プロセスによってスーパークラスチェーンが続行されます。

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

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

クラスで必要とされない場合は、コンビニエンスイニシャライザを提供する必要はありません。共通の初期化パターンへのショートカットが時間を節約するか、意図的にクラスの初期化をより明確にするときはいつでも、コンビニエンスイニシャライザを作成します。

指定イニシャライザとコンビニエンスイニシャライザの構文

クラスの指定イニシャライザは、値型の単純なイニシャライザと同じ方法で記述されます。

// 指定イニシャライザ
init(parameters) {
    statements
}

コンビニエンスイニシャライザは同じスタイルで記述されますが、convenience修飾子がinitスペースで区切られてキーワードの前に配置されます。

convenience init(parameters) {
    statements
}

クラス型のイニシャライザデリゲーション

指定イニシャライザとコンビニエンスイニシャライザの間の関係を簡略化するために、Swiftはイニシャライザ間のデリゲーション呼び出しに次の3つのルールを適用します。

  • ルール1 指定イニシャライザは、直接のスーパークラスから指定イニシャライザを呼び出す必要があります。
  • ルール2 コンビニエンスイニシャライザは、同じクラスから別のイニシャライザを呼び出す必要があります。
  • ルール3 コンビニエンスイニシャライザは、最終的に指定イニシャライザを呼び出す必要があります。

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

  • 指定イニシャライザは常に上位に委任する必要があります。
  • コンビニエンスイニシャライザは常に横に委譲する必要があります。

これらのルールを次の図に示します。../_images/initializerDelegation01_2x.png

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

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

注意

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

次の図は、4つのクラスのより複雑なクラス階層を示しています。この階層の指定イニシャライザがクラス初期化の「目標到達ポイント」として機能し、チェーン内のクラス間の相互関係を簡略化する方法を示しています。../_images/initializerDelegation02_2x.png

2フェーズ初期化

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

2フェーズの初期化プロセスを使用すると、初期化が安全になり、クラス階層の各クラスに完全な柔軟性が提供されます。2フェーズの初期化により、プロパティ値が初期化される前にアクセスされたり、プロパティ値が別のイニシャライザによって予期せず異なる値に設定されたりすることが防止されます。

注意

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

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

安全チェック1

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

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

安全チェック2

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

安全チェック3

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

安全チェック4

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

クラスインスタンスは、最初のフェーズが終了するまで完全に有効ではありません。最初のフェーズの終わりにクラスインスタンスが有効であることがわかった後は、プロパティにのみアクセスでき、メソッドを呼び出すことができます。

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

フェーズ1

  • 指定イニシャライザ、またはコンビニエンスイニシャライザがクラスで呼び出されます。
  • そのクラスの新しいインスタンスのメモリが割り当てられます。メモリはまだ初期化されていません。
  • そのクラスの指定イニシャライザは、そのクラスによって導入されたすべての格納されたプロパティに値があることを確認します。これらの保存されたプロパティのメモリが初期化されます。
  • 指定イニシャライザは、スーパークラスのイニシャライザに制御を渡し、独自の格納されたプロパティに対して同じタスクを実行します。
  • これは、チェーンの最上部に到達するまで、クラス継承チェーンを上に向かって続きます。
  • チェーンの最上部に到達し、チェーンの最後のクラスによって、格納されているすべてのプロパティに値があることが確認されると、インスタンスのメモリは完全に初期化されたと見なされ、フェーズ1が完了します。

フェーズ2

  • チェーンの先頭からさかのぼって、チェーン内の指定各イニシャライザには、インスタンスをさらにカスタマイズするオプションがあります。イニシャライザは、selfにアクセスしてそのプロパティを変更したり、インスタンスメソッドを呼び出したりできるようになります。
  • 最後に、チェーン内のコンビニエンスイニシャライザには、インスタンスをカスタマイズしてselfで作業するオプションがあります。

次に、フェーズ1が架空のサブクラスとスーパークラスの初期化呼び出しを探す方法を示します。../_images/twoPhaseInitialization01_2x.png

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

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

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

スーパークラスのすべてのプロパティに初期値があると、そのメモリは完全に初期化されたと見なされ、フェーズ1が完了します。

フェーズ2が同じ初期化呼び出しを探す方法は次のとおりです。../_images/twoPhaseInitialization02_2x.png

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

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

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

イニシャライザの継承とオーバーライド

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

注意

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

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

スーパークラスの指定イニシャライザと一致するサブクラスのイニシャライザを作成すると、その指定イニシャライザのオーバーライドを効果的に提供できます。したがって、overrideサブクラスのイニシャライザ定義の前に修飾子を記述する必要があります。これはで説明したように、自動的に提供されているデフォルトの初期化を上書きしている場合も同様である。デフォルトイニシャライザ参照

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

注意

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

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

次の例では、Vehicleという基本クラスを定義しています。この基本クラスは、デフォルトInt値が0のnumberOfWheelsと呼ばれる保存プロパティを宣言します。このnumberOfWheelsプロパティは、車両の特性を説明するString 型のdescriptionプロパティを作成するために呼び出される計算プロパティによって使用されます

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

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

let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)

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

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

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

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

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

let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// Bicycle: 2 wheel(s)

サブクラスイニシャライザが初期化プロセスのフェーズ2でカスタマイズを実行せず、スーパークラスにゼロ引数指定のイニシャライザがある場合は、サブクラスのすべての保存されているプロパティに値を割り当てた後、super.init()への呼び出しを省略できます。

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

class Hoverboard: Vehicle {
    var color: String
    init(color: String) {
        self.color = color
        // super.init() implicitly called here
    }
    override var description: String {
        return "\(super.description) in a beautiful \(color)"
    }
}

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

let hoverboard = Hoverboard(color: "silver")
print("Hoverboard: \(hoverboard.description)")
// Hoverboard: 0 wheel(s) in a beautiful silver

注意

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

自動的なイニシャライザの継承

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

サブクラスで導入するすべての新しいプロパティにデフォルト値を提供すると仮定すると、次の2つのルールが適用されます。

ルール1

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

ルール2

サブクラスがすべてのスーパークラスの指定イニシャライザの実装を提供する場合(ルール1に従って継承するか、定義の一部としてカスタム実装を提供することにより)、スーパークラスのすべての簡易イニシャライザを自動的に継承します。

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

注意

サブクラスは、ルール2を満たす一部として、サブクラスのコンビニエンスイニシャライザとしてスーパークラス指定のイニシャライザを実装できます。

指定イニシャライザとコンビニエンスイニシャライザの動作

次の例は、指定イニシャライザ、コンビニエンスイニシャライザ、および自動的なイニシャライザの継承の動作を示しています。この例では、と呼ばれる3つのクラスの階層FoodRecipeIngredientShoppingListItemを定義し、どのようにそのイニシャライザが相互作用するかを示しています。

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

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

次の図は、Foodクラスの初期化チェーンを示しています。../_images/initializersExample01_2x.png

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

let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon"

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

このFoodクラスは、引数のないコンビニエンスイニシャライザinit()も提供します。

[名前のない]値 nameで、Foodクラスのinit(name: String)に委譲することで、新たな食品のデフォルトのプレースホルダ名を提供します。

let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]"

階層の2番目のクラスRecipeIngredientは、Foodのサブクラスです。RecipeIngredientクラス、料理レシピの材料のモデルです。これは、quantityと呼ばれるIntプロパティ(それはnameプロパティに加えて、Foodから継承)と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クラスの初期化チェーンを示しています。../_images/initializersExample02_2x.png

RecipeIngredientクラスは、単一の指定イニシャライザinit(name: String, quantity: Int)を持っている。これは新しいRecipeIngredientインスタンスのすべてのプロパティを設定するために使用することができる。このイニシャライザは、渡されたquantity引数をquantityプロパティに割り当てることから始まります。これは、RecipeIngredientによって導入された唯一の新しいプロパティです。その後、イニシャライザはFoodクラスのinit(name: String)イニシャライザに委任します。このプロセスは、上記の2フェーズ初期化の安全性チェック1を満たします。

RecipeIngredientは、コンビニエンスイニシャライザinit(name: String)も定義しています。それは、名前だけでRecipeIngredientインスタンスを作成するために使用される

コンビニエンスイニシャライザは、明示的な量なしで作成されるRecipeIngredientインスタンスでは、数量1を想定している。このコンビニエンスイニシャライザの定義により、RecipeIngredientインスタンスの作成がより迅速かつ便利になり、複数の単一量のRecipeIngredientインスタンスを作成するときにコードの重複が回避されます。このコンビニエンスイニシャライザは、クラスの指定イニシャライザに委譲し、、1の値を渡します。

RecipeIngredientが提供する簡易イニシャライザinit(name: String)は、指定イニシャライザと同じパラメータinit(name: String)をFoodから取得します。このコンビニエンスイニシャライザはスーパークラスから指定イニシャライザをオーバーライドするため、override修飾子でマークする必要があります(Initializer Inheritance and Overriding参照)。

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

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

これら3つのイニシャライザはすべて、新しいRecipeIngredientインスタンスを作成するために使用できます。

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

階層内の3番目と最後のクラスはRecipeIngredientのサブクラスでShoppingListItemと呼ばれます。このShoppingListItemクラスは、買い物リストに表示されるレシピ成分をモデル化します。

買い物リストのすべてのアイテムは、「unpurchased」として始まります。この事実を表すために、では、デフォルト値がfalseの ShoppingListItemのpurchasedと呼ばれるBooleanプロパティが導入されています。ShoppingListItemはまた、ShoppingListItemインスタンスのテキストによる説明を提供するdescription計算プロパティを追加します。

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

注意

ShoppingListItemは、purchasedの初期値を提供するイニシャライザは定義していません。買い物リストのアイテム(ここでモデル化されている)は常にunpurchasedで開始されるためです。

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

次の図は、3つのクラスすべての初期化チェーン全体を示しています。../_images/initializersExample03_2x.png

継承された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 ✘

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

失敗可能なイニシャライザ

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

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

注意

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

失敗可能なイニシャライザは、初期化する型のオプションの値を作成します。失敗可能なイニシャライザ内に return nil を書き込んで、初期化の失敗をトリガーできるポイントを示します。

注意

厳密に言えば、イニシャライザは値を返しません。むしろ、それらの役割は、self が初期化が終了するまでに完全かつ正しく初期化されるようにすることです。初期化の失敗をトリガーするように return nil を書き込みますが、初期化の成功を示すために return キーワードを使用してはいけません。

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

let wholeNumber: Double = 12345.0
let pi = 3.14159

if let valueMaintained = Int(exactly: wholeNumber) {
    print("\(wholeNumber) conversion to Int maintains value of \(valueMaintained)")
}
// Prints "12345.0 conversion to Int maintains value of 12345"

let valueChanged = Int(exactly: pi)
// valueChanged is of type Int?, not Int

if valueChanged == nil {
    print("\(pi) conversion to Int does not maintain value")
}
// Prints "3.14159 conversion to Int does not maintain value"

次の例では、speciesという名前の定数Stringプロパティを持つAnimalと呼ばれる構造体を定義しています。この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 is of type Animal?, not Animal

if let giraffe = someCreature {
    print("An animal was initialized with a species of \(giraffe.species)")
}
// Prints "An animal was initialized with a species of Giraffe"

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

let anonymousCreature = Animal(species: "")
// anonymousCreature is of type Animal?, not Animal

if anonymousCreature == nil {
    print("The anonymous creature could not be initialized")
}
// Prints "The anonymous creature could not be initialized"

注意

(例えば、空の文字列の値をチェックする""のではなくする"Giraffe")ためのチェックと同じではないnilが存在しないことを示すために、任意 Stringの値を。上記の例では、空の文字列("")が有効で、オプションではありませんString。ただし、動物のspeciesプロパティの値として空の文字列を持つことは適切ではありません。この制限をモデル化するために、空の文字列が見つかった場合、失敗可能なイニシャライザは初期化の失敗をトリガーします。

列挙に失敗するイニシャライザ

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

以下の例では、TemperatureUnitと呼ばれるenum で3つの状態(kelvincelsius、およびfahrenheit)を定義する。失敗可能なイニシャライザは、温度シンボルを表す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("This is a defined temperature unit, so initialization succeeded.")
}
// Prints "This is a defined temperature unit, so initialization succeeded."

let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
    print("This is not a defined temperature unit, so initialization failed.")
}
// Prints "This is not a defined temperature unit, so initialization failed."

raw 値を持つ列挙型の失敗するイニシャライザ

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

上記のTemperatureUnitの例を書き直して、Character型の raw 値を使用し、init?(rawValue:)イニシャライザを利用することができます。

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

let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
    print("This is a defined temperature unit, so initialization succeeded.")
}
// Prints "This is a defined temperature unit, so initialization succeeded."

let unknownUnit = TemperatureUnit(rawValue: "X")
if unknownUnit == nil {
    print("This is not a defined temperature unit, so initialization failed.")
}
// Prints "This is not a defined temperature unit, so initialization failed."

初期化失敗の伝播

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

どちらの場合も、初期化が失敗する原因となる別のイニシャライザに委任すると、初期化プロセス全体がすぐに失敗し、それ以上の初期化コードは実行されません。

注意

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

以下の例は、ProductのサブクラスCartItemを定義します。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 値をチェックし、空の文字列の場合、イニシャライザプロセスはすぐに失敗します。

空ではない名前と数量1以上のCartItemインスタンスを作成すると、初期化は成功します。

if let twoSocks = CartItem(name: "sock", quantity: 2) {
    print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// Prints "Item: sock, quantity: 2"

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





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

if let oneUnnamed = CartItem(name: "", quantity: 1) {
    print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
    print("Unable to initialize one unnamed product")
}
// Prints "Unable to initialize one unnamed product"

失敗したイニシャライザのオーバーライド

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

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

注意

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

次の例では、Documentというクラスを定義しています。このクラスは、空でない文字列値またはnilのnameプロパティで初期化できるdocumentをモデル化しますが、空の文字列にすることはできません。

class Document {
    var name: String?
    // this initializer creates a document with a nil name value
    init() {}
    // this initializer creates a document with a nonempty name value
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

次の例では、DocumentのサブクラスAutomaticallyNamedDocumentを定義しています。AutomaticallyNamedDocumentサブクラスは、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
        }
    }
}

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

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

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

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

init!失敗するイニシャライザ

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

init?からinit!に、またその逆に委任することができます。さらに、init? をinit!へ、またはその逆にオーバーライドできます。しかし、そうすることにより、もしinit!イニシャライザによって初期化が失敗した場合にアサーションがトリガーされます。

必須イニシャライザ

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

class SomeClass {
    required init() {
        // initializer implementation goes here
    }
}

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

class SomeSubclass: SomeClass {
    required init() {
        // subclass implementation of the required initializer goes here
    }
}

注意

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

クロージャーまたは関数を使用したデフォルトのプロパティ値の設定

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

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

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

class SomeClass {
    let someProperty: SomeType = {
        // create a default value for someProperty inside this closure
        // someValue must be of the same type as SomeType
        return someValue
    }()
}

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

注意

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

次の例では、Chessboardチェスのゲームのボードをモデル化するという構造体を定義しています。チェスは8 x 8のボードで行われ、黒と白の正方形が交互に表示されます。../_images/chessBoard_2x.png

このゲームボードを表すために、Chessboard構造体にはBool値の64個の配列であるboardColorsと呼ばれる単一のプロパティがあります。配列の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))
// Prints "true"
print(board.squareIsBlackAt(row: 7, column: 7))
// Prints "false"

投稿者: admin

Free Software Engineer

コメントを残す

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