見出し画像

Swiftでプログラミング- Initialization 1

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

新しいインスタンスを作るためのメソッドです。イニシャライザ呼ばれ、定義しているプロパティを初期化をします。Objective-Cのイニシャライザとは異なり、Swiftのイニシャライザは値を返しません。その主な役割は、最初に使用される前に正しく初期化されることを保証することです。

クラス型のインスタンスはまた、そのクラスのインスタンスが解放される前に、任意のカスタムのクリーンアップを実行するデイニシャライズ を実装することができます。デイニシャライザはクラスのインスタンスが破棄される時に自動的に呼ばれる特殊なメソッドです。

Setting Initial Values for Stored Properties

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

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

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

Initializers

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

   init() {
       // perform some initialization here
   }

以下の例では、華氏で表される温度を格納するために、Fahrenheitという新しい構造体を定義しています。構造体Fahrenheitには、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"

この構造体では、パラメータを持たない1つのイニシャライザ「init」が定義されており、格納されている温度を32.0(華氏で表した水の凝固点)の値で初期化しています。

Default Property Values

保存プロパティの初期値は、上記のようにイニシャライザの中で設定することができます。また、デフォルトのプロパティ値をプロパティの宣言の一部として指定することもできます。既定のプロパティ値を指定するには、プロパティの定義時に初期値を割り当てる必要があります。

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

上述の構造体Fahrenheitは、プロパティの宣言時にtemperatureプロパティにデフォルト値を与えることで、よりシンプルな形で記述することができます。

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

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

Parameter Names and Argument Labels

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

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

次の例では、Red、Green、Blueと呼ばれる3つの定数プロパティを持つ、Colorと呼ばれる構造体を定義しています。これらのプロパティは、色の中の赤、緑、青の量を示す 0.0 から 1.0 の間の値を格納します。

Colorは、赤、緑、青の各成分に対して、Double型の適切な名前の3つのパラメータを持つイニシャライザを提供します。また、2つ目のイニシャライザには、1つの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)

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

    let veryGreen = Color(0.0, 1.0, 0.0)
   // this reports a compile-time error - argument labels are required

Initializer Parameters Without Argument Labels

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

ここでは、上記の「初期化パラメータ」の Celsius の例を拡張して、すでに 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)というイニシャライザの呼び出しは、引数ラベルを必要とせず、その意図が明確です。そのため、このイニシャライザを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: "Do you like cheese?")
   cheeseQuestion.ask()
   // Prints "Do you like cheese?"
   cheeseQuestion.response = "Yes, I do like cheese."

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

Assigning Constant Properties During Initialization

初期化が終了するまでに確定した値が設定されていれば、初期化中のどの時点でも定数プロパティに値を割り当てることができます。一度値を割り当てられた定数プロパティは、それ以上変更することはできません。

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

上記の SurveyQuestion の例を修正して、質問のテキストプロパティに変数プロパティではなく定数プロパティを使用することで、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.)"

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

構造体タイプは、独自のカスタムイニシャライザを定義していない場合、自動的にMemberwise Initializerを受け取ります。デフォルトのイニシャライザとは異なり、構造体は、デフォルト値を持たない保存プロパティがあっても、Memberwise Initializerを受け取ります。

Memberwise Initializerは、新しい構造体インスタンスのメンバー・プロパティを初期化するための省略可能な方法です。新しいインスタンスのプロパティの初期値は、名前を付けてメンバワイズ・イニシャライザに渡すことができます。

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

構造体Sizeは自動的にinit(width:height:)Memberwise Initializerを受け取り、これを使って新しいSizeインスタンスを初期化することができます。

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

Memberwise Initializerを呼び出すと、デフォルト値を持つプロパティの値を省略することができます。上の例では、Size 構造体の height および width プロパティの両方に既定値があります。どちらかのプロパティを省略することも、両方のプロパティを省略することもでき、イニシャライザは省略した分のデフォルト値を使用します。

   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"

Initializer Delegation for Value Types

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

イニシャライザのデリゲーションがどのように機能するか、また、どのような形式のデリゲーションが許されるかについてのルールは、バリュー型とクラス型で異なります。値型(構造体や列挙体)は継承をサポートしていないため、イニシャライザの委譲は比較的簡単です。なぜなら、自分で用意した別のイニシャライザにしか委譲できないからです。一方、クラスは、「継承」で説明したように、他のクラスを継承することができます。つまり、クラスには、継承したすべてのストアド・プロパティに初期化時に適切な値が割り当てられるようにするという追加の責任があります。これらの責任については、後述の「クラスの継承と初期化」で説明します。

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

値の型に対してカスタムイニシャライザを定義すると、その型のデフォルトのイニシャライザ(構造体の場合はメンバ単位のイニシャライザ)にはアクセスできなくなることに注意してください。この制約により、より複雑なイニシャライザで提供される追加の必須設定が、自動イニシャライザのいずれかを使用する人によって誤って回避されてしまう事態を防ぐことができます。

カスタム値型をデフォルトのイニシャライザとメンバワイズ・イニシャライザで初期化できるだけでなく、独自のカスタム・イニシャライザでも初期化できるようにしたい場合は、カスタム・イニシャライザを値型の元の実装の一部としてではなく、拡張機能に記述してください。詳細については、「拡張機能」を参照してください。

次の例では,幾何学的な長方形を表すカスタム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構造を初期化することができます。これらの初期化オプションは,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's origin is (0.0, 0.0) and its size is (0.0, 0.0)

2番目のRectイニシャライザであるinit(origin:size:)は、構造体が独自のカスタムイニシャライザを持っていなかった場合に受け取ることになるMemberwise Initializerと機能的には同じです。このイニシャライザは、単純に origin と size の引数値を適切な保存されたプロパティに割り当てます。

   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:)は、少し複雑です。これは、中心点とサイズ値に基づいて、適切な原点を計算することから始まります。その後、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:)イニシャライザが、まさにその機能をすでに提供している既存のイニシャライザを利用する方が便利です。

この記事が気に入ったらサポートをしてみませんか?