初期化
型の格納プロパティに初期値を設定し、一度だけのセットアップを行います。
初期化は、クラス、構造体、または列挙型のインスタンスを使用できるように準備するプロセスです。このプロセスには、そのインスタンスの各格納プロパティに初期値を設定し、新しいインスタンスが使用可能になる前に必要な他のセットアップや初期化を行うことが含まれます。
この初期化プロセスは、特定の型の新しいインスタンスを作成するために呼び出すことができる特別なメソッドのような初期化子を定義することで実装します。Objective-Cの初期化子とは異なり、Swiftの初期化子は値を返しません。初期化子の主な役割は、新しい型のインスタンスが初めて使用される前に正しく初期化されることを保証することです。
クラス型のインスタンスは、クラスのインスタンスが解放される直前にカスタムクリーンアップを実行するデイニシャライザを実装することもできます。デイニシャライザの詳細については、デイニシャライザを参照してください。
格納プロパティの初期値設定
クラスと構造体は、そのクラスまたは構造体のインスタンスが作成されるまでに、すべての格納プロパティに適切な初期値を設定する必要があります。格納プロパティを不確定な状態のままにすることはできません。
格納プロパティの初期値は、初期化子内で設定するか、プロパティの定義の一部としてデフォルトのプロパティ値を割り当てることで設定できます。これらのアクションは、以下のセクションで説明します。
注: 格納プロパティにデフォルト値を割り当てるか、初期化子内で初期値を設定する場合、そのプロパティの値はプロパティオブザーバを呼び出さずに直接設定されます。
初期化子
初期化子は、特定の型の新しいインスタンスを作成するために呼び出されます。最も単純な形式では、初期化子はパラメータのないインスタンスメソッドのようなもので、init
キーワードを使用して記述されます:
init() {
// ここで初期化を行う
}
以下の例では、華氏温度を表す温度を格納するために新しい構造体Fahrenheit
を定義しています。Fahrenheit
構造体には、Double
型の1つの格納プロパティtemperature
があります:
struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
print("デフォルトの温度は\(f.temperature)° Fahrenheitです")
// "デフォルトの温度は32.0° Fahrenheitです"と出力されます
この構造体は、パラメータのない単一の初期化子init
を定義しており、格納プロパティtemperature
を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は100.0です
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsiusは0.0です
最初の初期化子は、引数ラベルfromFahrenheit
とパラメータ名fahrenheit
を持つ単一の初期化パラメータを持ちます。2番目の初期化子は、引数ラベルfromKelvin
とパラメータ名kelvin
を持つ単一の初期化パラメータを持ちます。両方の初期化子は、単一の引数を対応する摂氏値に変換し、この値をtemperatureInCelsius
というプロパティに格納します。
パラメータ名と引数ラベル
関数やメソッドのパラメータと同様に、初期化パラメータには、初期化子の本体内で使用するパラメータ名と、初期化子を呼び出すときに使用する引数ラベルの両方を持つことができます。
ただし、初期化子は関数やメソッドのように括弧の前に識別関数名を持たないため、初期化子のパラメータの名前と型は、どの初期化子を呼び出すべきかを特定する上で特に重要な役割を果たします。このため、Swiftは、引数ラベルを提供しない場合、初期化子のすべてのパラメータに自動的に引数ラベルを提供します。
次の例では、Color
という構造体を定義し、red
、green
、blue
という3つの定数プロパティを持ちます。これらのプロパティは、色の赤、緑、青の量を示すために0.0から1.0の値を格納します。
Color
は、red
、green
、blue
コンポーネントのために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)
// これはコンパイル時エラーを報告します - 引数ラベルが必要です
引数ラベルのないイニシャライザパラメータ
イニシャライザパラメータに引数ラベルを使用したくない場合、そのパラメータの明示的な引数ラベルの代わりにアンダースコア(_
)を書いて、デフォルトの動作をオーバーライドします。
以下は、上記のイニシャライザパラメータの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
値を提供して呼び出すことができます。
オプショナルプロパティ型
カスタム型に「値がない」ことが論理的に許される格納プロパティがある場合 — 例えば、初期化中にその値を設定できない場合や、後で「値がない」ことが許される場合 — プロパティをオプショナル型で宣言します。オプショナル型のプロパティは自動的に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 = "はい、チーズが好きです。"
アンケートの質問に対する回答は、質問がされるまでわからないため、response
プロパティはString?
、つまり「オプショナルな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: "ビーツはどうですか?")
beetsQuestion.ask()
// "ビーツはどうですか?"と出力されます
beetsQuestion.response = "私もビーツが好きです。(でもチーズとは一緒にしません。)"
デフォルトイニシャライザ
Swiftは、すべてのプロパティにデフォルト値を提供し、少なくとも1つのイニシャライザを提供しない構造体またはクラスに対してデフォルトイニシャライザを提供します。デフォルトイニシャライザは、すべてのプロパティがデフォルト値に設定された新しいインスタンスを単に作成します。
次の例では、買い物リストのアイテムの名前、数量、および購入状態をカプセル化するShoppingListItem
というクラスを定義しています:
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
ShoppingListItem
クラスのすべてのプロパティにデフォルト値があり、スーパークラスを持たない基本クラスであるため、ShoppingListItem
は自動的にデフォルトイニシャライザの実装を取得し、すべてのプロパティがデフォルト値に設定された新しいインスタンスを作成します。(name
プロパティはオプショナルなString
プロパティであるため、この値がコードに書かれていなくても自動的にデフォルト値のnil
を受け取ります。)上記の例では、ShoppingListItem
クラスのデフォルトイニシャライザを使用して、イニシャライザ構文で新しいインスタンスを作成し、この新しいインスタンスをitem
という変数に割り当てています。
構造体型のメンバーワイズイニシャライザ
構造体型は、独自のカスタムイニシャライザを定義しない場合、自動的にメンバーワイズイニシャライザを受け取ります。デフォルトイニシャライザとは異なり、構造体は格納プロパティにデフォルト値がない場合でもメンバーワイズイニシャライザを受け取ります。
メンバーワイズイニシャライザは、新しい構造体インスタンスのメンバープロパティを初期化するための簡略化された方法です。新しいインスタンスのプロパティの初期値は、名前でメンバーワイズイニシャライザに渡すことができます。
次の例では、width
とheight
という2つのプロパティを持つSize
という構造体を定義しています。両方のプロパティは、デフォルト値の0.0を割り当てることでDouble
型と推論されます。
Size
構造体は自動的にinit(width:height:)
メンバーワイズイニシャライザを受け取り、これを使用して新しいSize
インスタンスを初期化できます:
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"と出力されます
値型のイニシャライザ委譲
イニシャライザは、インスタンスの初期化の一部を実行するために他のイニシャライザを呼び出すことができます。このプロセスはイニシャライザ委譲と呼ばれ、複数のイニシャライザ間でコードを重複させることを避けます。
イニシャライザ委譲の動作方法と許可される委譲の形式に関するルールは、値型とクラス型で異なります。値型(構造体と列挙型)は継承をサポートしていないため、彼らのイニシャライザ委譲プロセスは比較的簡単です。なぜなら、彼らは自分自身が提供する他のイニシャライザにのみ委譲できるからです。しかし、クラスは他のクラスから継承できるため、初期化中に継承するすべての格納プロパティに適切な値が割り当てられるようにする追加の責任があります。これらの責任については、以下のクラスの継承と初期化で説明します。
値型の場合、カスタムイニシャライザを書くときに、同じ値型の他のイニシャライザを参照するためにself.init
を使用します。self.init
はイニシャライザ内からのみ呼び出すことができます。
値型にカスタムイニシャライザを定義すると、その型のデフォルトイニシャライザ(または構造体の場合はメンバワイズイニシャライザ)にアクセスできなくなることに注意してください。この制約は、より複雑なイニシャライザで提供される追加の重要なセットアップが、自動イニシャライザの1つを使用することで誤って回避される状況を防ぎます。
注意: カスタム値型をデフォルトイニシャライザおよびメンバワイズイニシャライザで初期化可能にし、かつカスタムイニシャライザでも初期化可能にしたい場合は、カスタムイニシャライザを値型の元の実装の一部としてではなく、拡張として書いてください。詳細については、拡張を参照してください。
次の例では、幾何学的な長方形を表すカスタムRect
構造体を定義します。この例では、すべてのプロパティに0.0のデフォルト値を提供する2つのサポート構造体Size
とPoint
が必要です:
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
以下のRect
構造体は、デフォルトのゼロ初期化されたorigin
およびsize
プロパティ値を使用して初期化するか、特定の原点ポイントとサイズを提供して初期化するか、特定の中心ポイントとサイズを提供して初期化するかの3つの方法で初期化できます。これらの初期化オプションは、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()
は、構造体が独自のカスタムイニシャライザを持たない場合に受け取るデフォルトイニシャライザと機能的に同じです。このイニシャライザは空の中括弧{}
で表される空の本体を持っています。このイニシャライザを呼び出すと、origin
およびsize
プロパティがそれぞれのプロパティ定義から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:)
は少し複雑です。これは、中心点とサイズ値に基づいて適切な原点を計算することから始まります。その後、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:)
イニシャライザが既存のイニシャライザを利用する方が便利で(意図が明確で)あるため、既存のイニシャライザを利用することにしました。
注意:
init()
およびinit(origin:size:)
イニシャライザを自分で定義せずにこの例を書く別の方法については、拡張を参照してください。
クラスの継承と初期化
クラスのすべての格納プロパティ(スーパークラスから継承するプロパティを含む)は、初期化中に初期値を割り当てられる必要があります。
Swiftは、すべての格納プロパティが初期値を受け取ることを保証するために、クラス型に2種類のイニシャライザを定義しています。これらは指定イニシャライザとコンビニエンスイニシャライザとして知られています。
指定イニシャライザとコンビニエンスイニシャライザ
指定イニシャライザはクラスの主要なイニシャライザです。指定イニシャライザは、そのクラスによって導入されたすべてのプロパティを完全に初期化し、初期化プロセスをスーパークラスチェーンに沿って続行するために適切なスーパークラスのイニシャライザを呼び出します。
クラスは非常に少数の指定イニシャライザを持つ傾向があり、クラスが1つだけ持つことも非常に一般的です。指定イニシャライザは、初期化が行われる「ファネル」ポイントであり、初期化プロセスがスーパークラスチェーンに沿って続行されるポイントです。
すべてのクラスは少なくとも1つの指定イニシャライザを持っている必要があります。この要件は、以下の自動イニシャライザ継承で説明するように、スーパークラスから1つ以上の指定イニシャライザを継承することで満たされる場合があります。
コンビニエンスイニシャライザは、クラスの二次的なサポートイニシャライザです。コンビニエンスイニシャライザを定義して、指定イニシャライザの一部のパラメータをデフォルト値に設定して同じクラスの指定イニシャライザを呼び出すことができます。また、特定の使用ケースや入力値タイプのためにそのクラスのインスタンスを作成するためにコンビニエンスイニシャライザを定義することもできます。
クラスがコンビニエンスイニシャライザを必要としない場合は、提供する必要はありません。一般的な初期化パターンへのショートカットが時間を節約したり、クラスの初期化の意図を明確にしたりする場合に、コンビニエンスイニシャライザを作成します。
指定イニシャライザとコンビニエンスイニシャライザの構文
クラスの指定イニシャライザは、値型の単純なイニシャライザと同じ方法で書かれます:
init(<#parameters#>) {
<#statements#>
}
コンビニエンスイニシャライザは同じスタイルで書かれますが、convenience
修飾子がinit
キーワードの前にスペースで区切って配置されます:
convenience init(<#parameters#>) {
<#statements#>
}
クラスタイプのイニシャライザ委任
指定イニシャライザとコンビニエンスイニシャライザの関係を簡素化するために、Swiftはイニシャライザ間の委任呼び出しに対して次の3つのルールを適用します。
ルール1
指定イニシャライザは、直接のスーパークラスの指定イニシャライザを呼び出さなければなりません。
ルール2
コンビニエンスイニシャライザは、同じクラスの別のイニシャライザを呼び出さなければなりません。
ルール3
コンビニエンスイニシャライザは、最終的に指定イニシャライザを呼び出さなければなりません。
これを簡単に覚える方法は次の通りです:
- 指定イニシャライザは常に上に委任しなければなりません。
- コンビニエンスイニシャライザは常に横に委任しなければなりません。
これらのルールは以下の図で説明されています:
ここで、スーパークラスには1つの指定イニシャライザと2つのコンビニエンスイニシャライザがあります。1つのコンビニエンスイニシャライザが別のコンビニエンスイニシャライザを呼び出し、それが最終的に1つの指定イニシャライザを呼び出します。これにより、上記のルール2と3が満たされます。スーパークラス自体にはさらに上位のスーパークラスがないため、ルール1は適用されません。
この図のサブクラスには2つの指定イニシャライザと1つのコンビニエンスイニシャライザがあります。コンビニエンスイニシャライザは同じクラスの2つの指定イニシャライザのいずれかを呼び出さなければなりません。これは上記のルール2と3を満たします。両方の指定イニシャライザは、スーパークラスの1つの指定イニシャライザを呼び出さなければならず、これにより上記のルール1が満たされます。
注: これらのルールは、クラスのユーザーが各クラスのインスタンスを作成する方法には影響しません。上記の図のどのイニシャライザも、所属するクラスの完全に初期化されたインスタンスを作成するために使用できます。これらのルールは、クラスのイニシャライザの実装方法にのみ影響します。
以下の図は、4つのクラスのより複雑なクラス階層を示しています。この図は、この階層の指定イニシャライザがクラス初期化の「ファンネル」ポイントとして機能し、クラスチェーン間の相互関係を簡素化する様子を示しています。
二段階初期化
Swiftのクラス初期化は二段階のプロセスです。最初の段階では、各ストアドプロパティがそれを導入したクラスによって初期値を割り当てられます。すべてのストアドプロパティの初期状態が決定されると、第二段階が始まり、新しいインスタンスが使用可能と見なされる前に、各クラスがストアドプロパティをさらにカスタマイズする機会が与えられます。
二段階初期化プロセスの使用により、初期化が安全になり、クラス階層内の各クラスに完全な柔軟性を提供します。二段階初期化は、プロパティ値が初期化される前にアクセスされるのを防ぎ、別のイニシャライザによってプロパティ値が予期せず設定されるのを防ぎます。
注: Swiftの二段階初期化プロセスは、Objective-Cの初期化に似ています。主な違いは、第一段階中にObjective-Cはすべてのプロパティにゼロまたはヌル値(0やnilなど)を割り当てることです。Swiftの初期化フローは、カスタム初期値を設定でき、0やnilが有効なデフォルト値ではない型にも対応できるため、より柔軟です。
Swiftのコンパイラは、二段階初期化がエラーなく完了することを確認するために、4つの安全チェックを実行します:
安全チェック1
指定イニシャライザは、スーパークラスのイニシャライザに委任する前に、そのクラスによって導入されたすべてのプロパティが初期化されていることを確認しなければなりません。
前述のように、オブジェクトのメモリは、すべてのストアドプロパティの初期状態が知られるまで完全に初期化されたと見なされません。このルールを満たすために、指定イニシャライザは自分のプロパティがすべて初期化されていることを確認しなければなりません。
安全チェック2
指定イニシャライザは、継承されたプロパティに値を割り当てる前に、スーパークラスのイニシャライザに委任しなければなりません。そうしないと、指定イニシャライザが割り当てた新しい値が、スーパークラスの初期化の一部として上書きされます。
安全チェック3
コンビニエンスイニシャライザは、任意のプロパティ(同じクラスによって定義されたプロパティを含む)に値を割り当てる前に、別のイニシャライザに委任しなければなりません。そうしないと、コンビニエンスイニシャライザが割り当てた新しい値が、自分のクラスの指定イニシャライザによって上書きされます。
安全チェック4
イニシャライザは、初期化の第一段階が完了するまで、インスタンスメソッドを呼び出したり、インスタンスプロパティの値を読み取ったり、self
を値として参照したりすることはできません。
クラスインスタンスは、第一段階が終了するまで完全に有効ではありません。プロパティはアクセスできるようになり、メソッドはクラスインスタンスが第一段階の終了時に有効であると見なされた後にのみ呼び出すことができます。
以下は、上記の4つの安全チェックに基づいて、二段階初期化がどのように進行するかを示しています:
第一段階
- クラスの指定イニシャライザまたはコンビニエンスイニシャライザが呼び出されます。
- そのクラスの新しいインスタンスのメモリが割り当てられます。メモリはまだ初期化されていません。
- そのクラスの指定イニシャライザが、そのクラスによって導入されたすべてのストアドプロパティに値があることを確認します。これらのストアドプロパティのメモリは初期化されました。
- 指定イニシャライザは、スーパークラスのイニシャライザに引き継いで、同じタスクを実行します。
- これがクラス継承チェーンの最上位に達するまで続きます。
- チェーンの最上位に達し、チェーンの最後のクラスがすべてのストアドプロパティに値があることを確認すると、インスタンスのメモリは完全に初期化されたと見なされ、第一段階が完了します。
第二段階
- チェーンの最上位から下に向かって、チェーン内の各指定イニシャライザはインスタンスをさらにカスタマイズするオプションがあります。イニシャライザは
self
にアクセスでき、プロパティを変更したり、インスタンスメソッドを呼び出したりすることができます。 - 最後に、チェーン内の任意のコンビニエンスイニシャライザがインスタンスをカスタマイズし、
self
を操作するオプションがあります。
以下は、仮想のサブクラスとスーパークラスの初期化呼び出しに対する第一段階の様子です:
この例では、初期化はサブクラスのコンビニエンスイニシャライザの呼び出しから始まります。このコンビニエンスイニシャライザはまだプロパティを変更できません。同じクラスの指定イニシャライザに委任します。
指定イニシャライザは、安全チェック1に従って、サブクラスのすべてのプロパティに値があることを確認します。次に、初期化をチェーンの上に引き継ぐために、スーパークラスの指定イニシャライザを呼び出します。
スーパークラスの指定イニシャライザは、スーパークラスのすべてのプロパティに値があることを確認します。初期化するためのさらに上位のスーパークラスはなく、これ以上の委任は必要ありません。
スーパークラスのすべてのプロパティに初期値があるとすぐに、そのメモリは完全に初期化されたと見なされ、第一段階が完了します。
以下は、同じ初期化呼び出しに対する第二段階の様子です:
スーパークラスの指定イニシャライザは、インスタンスをさらにカスタマイズする機会があります(ただし、必須ではありません)。
スーパークラスの指定イニシャライザが終了すると、サブクラスの指定イニシャライザが追加のカスタマイズを行うことができます(ただし、これも必須ではありません)。
最後に、サブクラスの指定イニシャライザが終了すると、最初に呼び出されたコンビニエンスイニシャライザが追加のカスタマイズを行うことができます。
イニシャライザの継承とオーバーライド
Objective-Cのサブクラスとは異なり、Swiftのサブクラスはデフォルトでスーパークラスのイニシャライザを継承しません。Swiftのアプローチは、スーパークラスの単純なイニシャライザがより専門的なサブクラスによって継承され、そのサブクラスの新しいインスタンスが完全または正しく初期化されていない状態で作成される状況を防ぎます。
注: スーパークラスのイニシャライザは特定の状況下で継承されますが、それは安全で適切な場合に限ります。詳細については、以下の自動イニシャライザ継承を参照してください。
カスタムサブクラスがスーパークラスと同じイニシャライザを1つ以上提供したい場合は、そのイニシャライザのカスタム実装をサブクラス内に提供できます。
スーパークラスの指定イニシャライザと一致するサブクラスのイニシャライザを書くと、その指定イニシャライザのオーバーライドを提供していることになります。したがって、サブクラスのイニシャライザ定義の前にoverride
修飾子を書かなければなりません。これは、デフォルトイニシャライザで説明されているように、自動的に提供されるデフォルトイニシャライザをオーバーライドする場合でも同様です。
オーバーライドされたプロパティ、メソッド、またはサブスクリプトと同様に、override
修飾子の存在は、スーパークラスに一致する指定イニシャライザが存在することをSwiftに確認させ、オーバーライドするイニシャライザのパラメータが意図した通りに指定されていることを検証します。
注: スーパークラスの指定イニシャライザをオーバーライドする場合は常に
override
修飾子を書きます。たとえサブクラスのイニシャライザの実装がコンビニエンスイニシャライザであっても同様です。
逆に、スーパークラスのコンビニエンスイニシャライザと一致するサブクラスのイニシャライザを書く場合、そのスーパークラスのコンビニエンスイニシャライザは上記のクラス型のイニシャライザ委譲で説明されているルールに従ってサブクラスから直接呼び出すことはできません。したがって、サブクラスは(厳密には)スーパークラスのイニシャライザのオーバーライドを提供しているわけではありません。その結果、スーパークラスのコンビニエンスイニシャライザと一致する実装を提供する場合はoverride
修飾子を書きません。
以下の例では、Vehicle
という基本クラスを定義しています。この基本クラスは、デフォルトのInt
値が0のnumberOfWheels
という格納プロパティを宣言しています。numberOfWheels
プロパティは、車両の特性を説明するString
を作成するために使用される計算プロパティdescription
によって使用されます:
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}
Vehicle
クラスは唯一の格納プロパティにデフォルト値を提供し、カスタムイニシャライザを提供していません。その結果、デフォルトイニシャライザで説明されているように、自動的にデフォルトイニシャライザを受け取ります。デフォルトイニシャライザ(利用可能な場合)は常にクラスの指定イニシャライザであり、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
のスーパークラスの指定イニシャライザと一致するため、このイニシャライザはoverride
修飾子でマークされています。
Bicycle
のinit()
イニシャライザはsuper.init()
を呼び出すことから始まり、Bicycle
クラスのスーパークラスであるVehicle
のデフォルトイニシャライザを呼び出します。これにより、Vehicle
によって継承されたnumberOfWheels
プロパティが初期化され、Bicycle
がプロパティを変更する機会を得る前に初期化されます。super.init()
を呼び出した後、numberOfWheels
の元の値は2に置き換えられます。
Bicycle
のインスタンスを作成すると、継承されたdescription
計算プロパティを呼び出して、numberOfWheels
プロパティがどのように更新されたかを確認できます:
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// Bicycle: 2 wheel(s)
サブクラスのイニシャライザが初期化プロセスのフェーズ2でカスタマイズを行わず、スーパークラスに同期的な引数なしの指定イニシャライザがある場合、サブクラスのすべての格納プロパティに値を割り当てた後にsuper.init()
の呼び出しを省略できます。スーパークラスのイニシャライザが非同期の場合は、明示的にawait super.init()
と書く必要があります。
この例では、Vehicle
の別のサブクラスであるHoverboard
を定義しています。Hoverboard
クラスのイニシャライザでは、color
プロパティのみを設定します。明示的にsuper.init()
を呼び出す代わりに、このイニシャライザはスーパークラスのイニシャライザの暗黙的な呼び出しに依存してプロセスを完了します。
class Hoverboard: Vehicle {
var color: String
init(color: String) {
self.color = color
// ここでsuper.init()が暗黙的に呼び出されます
}
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を満たす一環として、スーパークラスの指定イニシャライザをサブクラスのコンビニエンスイニシャライザとして実装できます。
指定イニシャライザとコンビニエンスイニシャライザの実際
次の例は、指定イニシャライザ、コンビニエンスイニシャライザ、および自動イニシャライザ継承の実際を示しています。この例では、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の名前は "Bacon"
Food
クラスの init(name: String)
イニシャライザは指定イニシャライザとして提供されており、新しい Food
インスタンスのすべての保存プロパティが完全に初期化されることを保証します。Food
クラスにはスーパークラスがないため、init(name: String)
イニシャライザは初期化を完了するために super.init()
を呼び出す必要はありません。
Food
クラスは引数なしのコンビニエンスイニシャライザ init()
も提供します。init()
イニシャライザは、Food
クラスの init(name: String)
に名前の値 [Unnamed]
を渡して委譲することで、新しい食品にデフォルトのプレースホルダー名を提供します:
let mysteryMeat = Food()
// mysteryMeatの名前は "[Unnamed]"
階層の2番目のクラスは Food
のサブクラスである RecipeIngredient
です。RecipeIngredient
クラスは料理のレシピの材料をモデル化します。RecipeIngredient
は quantity
という Int
プロパティを導入し(Food
から継承した name
プロパティに加えて)、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
クラスには、RecipeIngredient
インスタンスのすべてのプロパティを設定するために使用できる単一の指定イニシャライザ init(name: String, quantity: Int)
があります。このイニシャライザは、渡された quantity
引数を quantity
プロパティに割り当てることから始めます。これは RecipeIngredient
によって導入された唯一の新しいプロパティです。その後、イニシャライザは Food
クラスの init(name: String)
イニシャライザに委譲します。このプロセスは、上記の二段階初期化の安全チェック1を満たします。
RecipeIngredient
は、名前だけで RecipeIngredient
インスタンスを作成するためのコンビニエンスイニシャライザ init(name: String)
も定義しています。このコンビニエンスイニシャライザは、明示的な数量なしで作成された RecipeIngredient
インスタンスに対して数量1を仮定します。このコンビニエンスイニシャライザの定義により、RecipeIngredient
インスタンスの作成が迅速かつ便利になり、複数の単一数量の RecipeIngredient
インスタンスを作成する際のコードの重複を避けることができます。このコンビニエンスイニシャライザは、数量値1を渡してクラスの指定イニシャライザに単に委譲します。
RecipeIngredient
によって提供される init(name: String)
コンビニエンスイニシャライザは、スーパークラスの指定イニシャライザ init(name: String)
と同じパラメータを取ります。このコンビニエンスイニシャライザはスーパークラスの指定イニシャライザをオーバーライドするため、override
修飾子を付ける必要があります(イニシャライザの継承とオーバーライドで説明されています)。
RecipeIngredient
は init(name: String)
イニシャライザをコンビニエンスイニシャライザとして提供していますが、それでもスーパークラスのすべての指定イニシャライザの実装を提供しています。したがって、RecipeIngredient
はスーパークラスのすべてのコンビニエンスイニシャライザも自動的に継承します。
この例では、RecipeIngredient
のスーパークラスは Food
であり、init()
という単一のコンビニエンスイニシャライザを持っています。このイニシャライザは RecipeIngredient
によって継承されます。継承された init()
のバージョンは Food
バージョンとまったく同じ方法で機能しますが、Food
バージョンではなく RecipeIngredient
バージョンの init(name: String)
に委譲します。
これら3つのイニシャライザすべてを使用して、新しい RecipeIngredient
インスタンスを作成できます:
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
階層の3番目で最後のクラスは RecipeIngredient
のサブクラスである ShoppingListItem
です。ShoppingListItem
クラスは、買い物リストに表示されるレシピの材料をモデル化します。
買い物リストのすべてのアイテムは「未購入」として開始されます。この事実を表すために、ShoppingListItem
はデフォルト値が false
の purchased
というブールプロパティを導入します。ShoppingListItem
はまた、ShoppingListItem
インスタンスのテキスト記述を提供する計算プロパティ description
を追加します:
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ✔" : " ✘"
return output
}
}
注:
ShoppingListItem
はpurchased
の初期値を提供するためのイニシャライザを定義していません。なぜなら、ここでモデル化されているように、買い物リストのアイテムは常に未購入の状態で開始されるからです。
導入されたすべてのプロパティにデフォルト値を提供し、独自のイニシャライザを定義していないため、ShoppingListItem
はスーパークラスのすべての指定イニシャライザとコンビニエンスイニシャライザを自動的に継承します。
以下の図は、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" に変更され、購入済みとしてマークされます。配列内の各アイテムの説明を印刷すると、デフォルトの状態が期待どおりに設定されていることがわかります。
失敗可能イニシャライザ
クラス、構造体、または列挙型の初期化が失敗する可能性がある場合があります。この失敗は、無効な初期化パラメータ値、必要な外部リソースの欠如、または初期化の成功を妨げる他の条件によって引き起こされることがあります。
失敗する可能性のある初期化条件に対処するために、クラス、構造体、または列挙型の定義の一部として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)の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
という1つのパラメータを持つ失敗可能イニシャライザも定義します。このイニシャライザは、初期化パラメータとして渡された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("匿名の生物は初期化できませんでした")
}
// "匿名の生物は初期化できませんでした"と出力されます
注: 空文字列(
""
)を確認すること("Giraffe"
ではなく)は、オプショナルなString
値の欠如を示すnil
を確認することと同じではありません。上記の例では、空文字列(""
)は有効な非オプショナルなString
です。しかし、動物のspecies
プロパティの値として空文字列を持つことは適切ではありません。この制限をモデル化するために、失敗可能イニシャライザは空文字列が見つかった場合に初期化失敗を引き起こします。
列挙型の失敗可能イニシャライザ
失敗可能イニシャライザを使用して、1つ以上のパラメータに基づいて適切な列挙型ケースを選択できます。提供されたパラメータが適切な列挙型ケースと一致しない場合、イニシャライザは失敗することがあります。
以下の例では、TemperatureUnit
という列挙型を定義し、3つの可能な状態(kelvin
、celsius
、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("これは定義された温度単位なので、初期化に成功しました。")
}
// "これは定義された温度単位なので、初期化に成功しました。"と出力されます
let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
print("これは定義されていない温度単位なので、初期化に失敗しました。")
}
// "これは定義されていない温度単位なので、初期化に失敗しました。"と出力されます
生の値を持つ列挙型の失敗可能イニシャライザ
生の値を持つ列挙型は、自動的に失敗可能イニシャライザinit?(rawValue:)
を受け取ります。このイニシャライザは、適切な生の値型のrawValue
というパラメータを取り、一致する列挙型ケースが見つかった場合はそれを選択し、一致する値が存在しない場合は初期化失敗を引き起こします。
上記のTemperatureUnit
の例を、Character
型の生の値を使用するように書き直し、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("これは定義されていない温度単位なので、初期化に失敗しました。")
}
// "これは定義されていない温度単位なので、初期化に失敗しました。"と出力されます
初期化失敗の伝播
クラス、構造体、または列挙型の失敗可能イニシャライザは、同じクラス、構造体、または列挙型の別の失敗可能イニシャライザに委譲することができます。同様に、サブクラスの失敗可能イニシャライザは、スーパークラスの失敗可能イニシャライザに委譲することができます。
いずれの場合も、初期化が失敗する原因となる別のイニシャライザに委譲すると、初期化プロセス全体が直ちに失敗し、それ以上の初期化コードは実行されません。
注意: 失敗可能イニシャライザは、失敗しないイニシャライザに委譲することもできます。このアプローチを使用すると、通常は失敗しない既存の初期化プロセスに潜在的な失敗状態を追加することができます。
以下の例では、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以上であることを確認することから始まります。数量が無効な場合、初期化プロセス全体が直ちに失敗し、それ以上の初期化コードは実行されません。同様に、Product
の失敗可能イニシャライザは名前の値をチェックし、名前が空文字列の場合、初期化プロセスは直ちに失敗します。
非空の名前と1以上の数量でCartItem
インスタンスを作成すると、初期化は成功します。
if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// "Item: sock, quantity: 2"と出力されます
数量が0の値でCartItem
インスタンスを作成しようとすると、CartItem
イニシャライザが初期化を失敗させます。
if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
print("Unable to initialize zero shirts")
}
// "Unable to initialize zero shirts"と出力されます
同様に、空の名前の値でCartItem
インスタンスを作成しようとすると、スーパークラスのProduct
イニシャライザが初期化を失敗させます。
if let oneUnnamed = CartItem(name: "", quantity: 1) {
print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
print("Unable to initialize one unnamed product")
}
// "Unable to initialize one unnamed product"と出力されます
失敗可能イニシャライザのオーバーライド
他のイニシャライザと同様に、サブクラスでスーパークラスの失敗可能イニシャライザをオーバーライドすることができます。あるいは、サブクラスの失敗しないイニシャライザでスーパークラスの失敗可能イニシャライザをオーバーライドすることもできます。これにより、スーパークラスの初期化が失敗する可能性がある場合でも、サブクラスの初期化が失敗しないように定義することができます。
スーパークラスの失敗可能イニシャライザをサブクラスの失敗しないイニシャライザでオーバーライドする場合、スーパークラスのイニシャライザの結果を強制アンラップして委譲する必要があります。
注意: 失敗可能イニシャライザを失敗しないイニシャライザでオーバーライドすることはできますが、その逆はできません。
以下の例では、Document
というクラスを定義しています。このクラスは、非空の文字列値またはnil
のname
プロパティで初期化できるドキュメントをモデル化していますが、空文字列は許可されません。
class Document {
var name: String?
// このイニシャライザはnilのname値でドキュメントを作成します
init() {}
// このイニシャライザは非空のname値でドキュメントを作成します
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
次の例では、Document
のサブクラスであるAutomaticallyNamedDocument
を定義しています。AutomaticallyNamedDocument
サブクラスは、Document
によって導入された指定イニシャライザの両方をオーバーライドします。これらのオーバーライドにより、AutomaticallyNamedDocument
インスタンスは、名前なしで初期化された場合、またはinit(name:)
イニシャライザに空文字列が渡された場合に、初期の名前値として"[Untitled]"を持つことが保証されます。
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:)
イニシャライザが空文字列として名前を渡された場合、強制アンラップ操作はランタイムエラーを引き起こします。しかし、文字列定数で呼び出されるため、このイニシャライザが失敗しないことがわかり、この場合ランタイムエラーは発生しません。
init!
失敗可能イニシャライザ
通常、init
キーワードの後に疑問符 (init?
) を付けることで、適切な型のオプショナルインスタンスを作成する失敗可能イニシャライザを定義します。代わりに、init
キーワードの後に疑問符の代わりに感嘆符 (init!
) を付けることで、暗黙的にアンラップされたオプショナルインスタンスを作成する失敗可能イニシャライザを定義することもできます。
init?
から init!
へ、またはその逆に委譲することができ、init?
を init!
でオーバーライドすることも、その逆も可能です。また、init
から init!
へ委譲することもできますが、init!
イニシャライザが初期化に失敗するとアサーションが発生します。
必須イニシャライザ
クラスのイニシャライザの定義の前に required
修飾子を記述して、そのクラスのすべてのサブクラスがそのイニシャライザを実装しなければならないことを示します:
class SomeClass {
required init() {
// イニシャライザの実装がここに入ります
}
}
また、必須イニシャライザのサブクラス実装の前にも required
修飾子を記述して、チェーン内のさらに下位のサブクラスにもイニシャライザの要件が適用されることを示します。必須の指定イニシャライザをオーバーライドする場合、override
修飾子は記述しません:
class SomeSubclass: SomeClass {
required init() {
// 必須イニシャライザのサブクラス実装がここに入ります
}
}
注: 継承されたイニシャライザで要件を満たすことができる場合、必須イニシャライザの明示的な実装を提供する必要はありません。
クロージャまたは関数を使用してデフォルトプロパティ値を設定する
格納プロパティのデフォルト値にカスタマイズやセットアップが必要な場合、クロージャやグローバル関数を使用してそのプロパティのカスタマイズされたデフォルト値を提供できます。そのプロパティが属する型の新しいインスタンスが初期化されるたびに、クロージャや関数が呼び出され、その戻り値がプロパティのデフォルト値として割り当てられます。
この種のクロージャや関数は通常、プロパティと同じ型の一時的な値を作成し、その値を望ましい初期状態を表すように調整し、その一時的な値をプロパティのデフォルト値として使用するために返します。
以下は、クロージャを使用してデフォルトプロパティ値を提供する方法の骨組みです:
class SomeClass {
let someProperty: SomeType = {
// このクロージャ内で someProperty のデフォルト値を作成します
// someValue は SomeType と同じ型でなければなりません
return someValue
}()
}
クロージャの終了中括弧の後に空の括弧のペアが続くことに注意してください。これは Swift にクロージャを即座に実行するように指示します。これらの括弧を省略すると、クロージャ自体をプロパティに割り当てようとしていることになり、クロージャの戻り値ではありません。
注: プロパティを初期化するためにクロージャを使用する場合、そのクロージャが実行される時点ではインスタンスの残りの部分はまだ初期化されていないことを覚えておいてください。これは、クロージャ内から他のプロパティ値にアクセスできないことを意味します。たとえそれらのプロパティにデフォルト値が設定されていても同様です。また、暗黙の
self
プロパティを使用したり、インスタンスのメソッドを呼び出したりすることもできません。
以下の例では、チェスゲームのボードをモデル化する Chessboard
構造体を定義しています。チェスは、黒と白のマスが交互に並ぶ 8 x 8 のボードでプレイされます。
このゲームボードを表すために、Chessboard
構造体には boardColors
という単一のプロパティがあり、これは 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" を出力