初期化(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 は、イニシャライザが提供されていない場合、イニシャライザの全てのパラメータに自動で引数ラベルを提供します。
次の例では、red
、green
、blue
という 3 つの定数プロパティを持つ Color
という構造体を定義しています。これらのプロパティには、色の赤、緑、青の量を示す 0.0
~ 1.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)
構造体は、独自のカスタムイニシャライザを定義していない場合、メンバワイズイニシャライザを自動的に定義されます。デフォルトイニシャライザとは異なり、構造体は、デフォルト値を持たないプロパティが格納されている場合でも、メンバワイズイニシャライザが定義されます。
メンバワイズイニシャライザは、新しい構造体インスタンスのプロパティを初期化する簡単な方法です。新しいインスタンスのプロパティの初期値は、プロパティ名によってメンバワイズイニシャライザに渡すことができます。
下記の例では、width
と height
という 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
構造体の height
と width
の両方のプロパティにデフォルト値があります。プロパティのいずれかまたは両方を省略できます。イニシャライザは、省略した全てのプロパティにデフォルト値を使用します。例えば、次のようになります:
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
構造体を定義しています。この例では、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
構造体を初期化できます。つまり、0
で初期化されたデフォルトの origin
と size
のプロパティ値を使用するか、特定の原点とサイズを指定するか、特定の中心点とサイズを指定します。これらの初期化の選択肢は、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:)
は、もう少し複雑です。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 の origin は (2.5, 2.5) で size は (3.0, 3.0)
init(center:size:)
イニシャライザを使っても、origin
と size
の新しい値を適切なプロパティに割り当てることができます。ただし、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 がゼロまたはヌル値 (0
やnil
など) を全てのプロパティに割り当てることです。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(デフォルトイニシャライザ)で説明されているように、デフォルトのイニシャライザを自動的に受け取ります。デフォルトイニシャライザは、(使用可能な場合)常にクラスの指定イニシャライザで、numberOfWheels
が 0
の新しい Vehicle
インスタンスを作成するために使用できます:
let vehicle = Vehicle()
print("乗り物: \(vehicle.description)")
// 乗り物: 0 輪車
次の例では、Bicycle
という Vehicle
のサブクラスを定義しています:
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.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 イニシャライザ、および自動イニシャライザの継承の挙動を示しています。この例では、Food
、RecipeIngredient
、および 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 イニシャライザは、単にクラスの指定イニシャライザに委譲し、quantity
に 1
を渡します。
RecipeIngredient
によって提供される init(name: String)
convenience イニシャライザは、Food
の init(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 つの可能な状態(kelvin
、celsius
、fahrenheit
)を使用して、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
が空でなく、quantity
が 1
以上の CartItem
インスタンスを作成すると、初期化は成功します:
if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("商品: \(twoSocks.name), 在庫数: \(twoSocks.quantity)")
}
// 商品: sock, 在庫数: 2
quantity
の値が 0
の CartItem
インスタンスを作成しようとすると、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