初期化

初期化は、クラス、構造体、または 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"

継承

クラスは、メソッド、プロパティ、およびその他の特性を別のクラスから継承できます。あるクラスが別のクラスを継承する場合、継承するクラスはサブクラスと呼ばれ、継承するクラスはスーパークラスと呼ばれます。継承は、クラスをSwiftの他の型と区別する基本的な動作です。

Swiftのクラスは、スーパークラスに属するメソッド、プロパティ、およびサブスクリプトを呼び出してアクセスでき、それらのメソッド、プロパティ、およびサブスクリプトの独自のオーバーライドバージョンを提供して、動作を調整または変更できます。Swiftは、オーバーライド定義に一致するスーパークラス定義があることを確認することにより、オーバーライドが正しいことを確認するのに役立ちます。

クラスは、プロパティの値が変更されたときに通知を受けるために、継承されたプロパティにプロパティオブザーバーを追加することもできます。プロパティオブザーバーは、格納されたプロパティまたは計算されたプロパティとして最初に定義されたかどうかに関係なく、任意のプロパティに追加できます。

ベースクラスの定義

別のクラスを継承しないクラスは、ベースクラスと呼ばれます。

注意

Swiftクラスは、普遍的なベースクラスを継承しません。スーパークラスを指定せずに定義したクラスは、自動的にベースクラスになります。

// ベースクラスの例
class Vehicle {
    var currentSpeed = 0.0
    var description: String {
        return "traveling at \(currentSpeed) miles per hour"
    }
    func makeNoise() {
        // do nothing - an arbitrary vehicle doesn't necessarily make a noise
    }
}

// 新しいインスタンスを作成
let someVehicle = Vehicle()
print("someVehicle.description")

サブクラス化

サブクラス化とは、既存のクラスに基づいて新しいクラスを作成することです。サブクラスは、既存のクラスから特性を継承し、新しい特性を追加することもできます。

// サブクラスの定義:スーパークラス名の前にコロンで区切ってサブクラス名を記述
class SomeSubclass: SomeSuperclass {
    // subclass definition goes here
}

// 例
class Bicycle: Vehicle {
    var hasBasket = false
}

let bicycle = Bicycle()
bicycle.hasBasket = true
bicycle.currentSpeed = 15.0

// 例:サブクラス自体をサブクラス化
class Tandem: Bicycle {
    var currentNumberOfPassengers = 0
}

let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print("tandem.description")

オーバーライド

サブクラスは、スーパークラスから継承するインスタンスメソッド、タイプメソッド、インスタンスプロパティ、タイププロパティ、またはサブスクリプトの独自のカスタム実装を提供できます。これはオーバーライドと呼ばれます

継承される特性をオーバーライドするには、オーバーライド定義の前にoverrideキーワードを付けます。overrideキーワードのないオーバーライドは、コードのコンパイル時にエラーになります。

スーパークラスのメソッド、プロパティ、およびサブスクリプトへのアクセス

super接頭辞を使用して、メソッド、プロパティ、またはサビスクリプトのスーパークラスバージョンにアクセスします。

  • 名前付きのオーバーライドされたメソッド: super.someMethod()
  • 呼び出されたオーバーライドされたプロパティ: super.someProperty
  • オーバーライドされたサブスクリプト: オーバーライドされたサブスクリプト実装内で super[someIndex] として呼び出す

メソッドのオーバーライド

継承されたインスタンスまたはタイプメソッドをオーバーライドして、サブクラス内のメソッドの調整済み実装または代替実装を提供できます。

次の例では、新しいサブクラス定義Vehicle呼ばTrainオーバーライド、makeNoise()方法をそのTrainから継承Vehicle

// メソッドのオーバーライド例
class Train: Vehicle {
    override func makeNoise() {
        print("Choo Choo")
    }
}

let train = Train()
train.makeNoise()
// Prints "Choo Choo"

プロパティのオーバーライド

継承されたインスタンスまたはタイププロパティをオーバーライドして、そのプロパティに独自のカスタムゲッターとセッターを提供したり、プロパティオブザーバーを追加して、基になるプロパティ値が変更されたときにオーバーライドするプロパティを監視できるようにすることができます。

プロパティのゲッターとセッターのオーバーライド

継承されたプロパティがソースで格納または計算されたプロパティとして実装されているかどうかに関係なく、カスタムゲッター(および適切な場合はセッター)を提供して、継承されたプロパティをオーバーライドできます。継承されたプロパティの格納または計算された性質は、サブクラスでは認識されません。継承されたプロパティが特定の名前と型を持っていることだけがわかります。オーバーライドが同じ名前とタイプのスーパークラスプロパティに一致することをコンパイラがチェックできるようにするには、オーバーライドするプロパティの名前とタイプの両方を常に指定する必要があります。

サブクラスプロパティのオーバーライドでゲッターとセッターの両方を提供することにより、継承された読み取り専用プロパティを読み取り/書き込みプロパティとして提示できます。ただし、継承された読み取り/書き込みプロパティを読み取り専用プロパティとして提示することはできません。

注意

プロパティのオーバーライドの一部としてセッターを提供する場合は、そのオーバーライドのゲッターも提供する必要があります。オーバーライドするゲッター内で継承されたプロパティの値を変更したくない場合は、ゲッターから戻ることで継承された値super.somePropertyを単に渡すことができます。ここで、somePropertyはオーバーライドするプロパティの名前です。

プロパティオブザーバーのオーバーライド

プロパティのオーバーライドを使用して、継承されたプロパティにプロパティオブザーバーを追加できます。これにより、プロパティが最初にどのように実装されたかに関係なく、継承されたプロパティの値が変更されたときに通知を受けることができます。

注意

継承された定数格納プロパティまたは継承された読み取り専用の計算プロパティにプロパティオブザーバーを追加することはできません。これらのプロパティの値は設定できないため、オーバーライドの一部としてwillSetまたはdidSet実装を提供することは適切ではありません。

また、同じプロパティにオーバーライドセッターとオーバーライドプロパティオブザーバーの両方を指定することはできません。プロパティの値の変更を監視する必要があり、そのプロパティのカスタムセッターを既に提供している場合は、カスタムセッター内から値の変更を簡単に監視できます。

// プロパティオブザーバーのオーバーライドの例
class AutomaticCar: Car {
    override var currentSpeed: Double {
        didSet {
            gear = Int(currentSpeed / 10.0) + 1
        }
    }
}

let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
// AutomaticCarインスタンスのcurrentSpeed を設定するたび、プロパティのdidSetオブザーバーは、インスタンスのgearプロパティを適切なギアにセットします。
// AutomaticCar: traveling at 35.0 miles per hour in gear 4

オーバーライドの防止

メソッド、プロパティ、またはサブスクリプトが final としてマークされることにより、それらがオーバーライドされるのを防ぐことができます。

// オーバーライドを禁止するキーワード final
final var
final func
final class func
final subscript

サブクラスのfinalメソッド、プロパティ、またはサブスクリプトをオーバーライドしようとすると、コンパイル時エラーとなります。

クラス定義でキーワードの前に final 修飾子を記述することにより、クラス全体を最終としてマークできます。final クラスをサブクラス化しようとすると、コンパイル時エラーとして報告されます。

サブスクリプト

クラス、構造体、および列挙体は、コレクション、リスト、またはシーケンスのメンバー要素にアクセスするためのショートカットであるサブスクリプトを定義できます。サブスクリプトを使用すると、設定と取得のための個別のメソッドを必要とせずに、インデックスによって値を設定および取得できます。たとえば、Arrayインスタンスの要素にはsomeArray[index]として、Dictionaryインスタンスの要素にはsomeDictionary[key]としてアクセスします。

単一のタイプに対して複数のサブスクリプトを定義できます。使用する適切なサブスクリプトオーバーロードは、サブスクリプトに渡すインデックス値のタイプに基づいて選択されます。サブスクリプトは単一の次元に限定されず、カスタムタイプのニーズに合わせて複数の入力パラメーターでサブスクリプトを定義できます。

サブスクリプト構文

インスタンスメソッドとは異なり、サブスクリプトは読み取り/書き込みまたは読み取り専用にすることができます。この動作は、計算されたプロパティの場合と同じ方法でゲッターとセッターによって伝達されます。

subscript(index: Int) -> Int {
    get {
        // Return an appropriate subscript value here.
    }
    set(newValue) {
        // Perform a suitable setting action here.
    }
}

// 読み取り専用のサブスクリプトの宣言
subscript(index: Int) -> Int {
    // Return an appropriate subscript value here.
}

// 読み取り専用のサブスクリプトの例
struct TimesTable {
    let multiplier: Int
    subscript(index: Int) -> Int {
        return multiplier * index
    }
}
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
// Prints "six times three is 18"

サブスクリプトの使用

「サブスクリプト」の正確な意味は、それが使用されるコンテキストによって異なります。サブスクリプトは通常、コレクション、リスト、またはシーケンスのメンバー要素にアクセスするためのショートカットとして使用されます。特定のクラスまたは struct の機能に最適な方法でサブスクリプトを自由に実装できます。

たとえば、SwiftのDictionary型は、Dictionaryインスタンスに格納されている値を設定および取得するためのサブスクリプトを実装しています。サブスクリプトの括弧内にDictionaryのキーを提供し、ディクショナリに値を設定できます。

var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
numberOfLegs["bird"] = 2

注意

SwiftのDictionary型は、キーと値のサブスクリプトを、オプションの型を受け取って返すサブスクリプトとして実装します。numberOfLegs上記のディクショナリの場合、キーと値のサブスクリプトは Int?、つまり「オプショナルint」を受け取って返します。

サブスクリプトオプション

サブスクリプトは任意の数の入力パラメーターを取ることができ、これらの入力パラメーターは任意のタイプにすることができます。サブスクリプトは、任意のタイプの値を返すこともできます。

関数と同様に、添字はさまざまな数のパラメーターを受け取り、それらのパラメーターにデフォルト値を提供できます。ただし、関数とは異なり、サブスクリプトは入出力パラメーターを使用できません。

クラスまたは構造体は、必要なだけのサブスクリプト実装を提供できます。使用される適切なサブスクリプトは、サブスクリプトが使用されているポイントでサブスクリプト括弧内に含まれている値のタイプに基づいて推測されます。この複数のサブスクリプトの定義は、サブスクリプトオーバーロードと呼ばれます

サブスクリプトが単一のパラメーターを取るのが最も一般的ですが、複数のパラメーターを持つサブスクリプトを定義することもできます。次の例では、Doubleの2次元行列を表す構造を定義しています。Matrix構造のサブスクリプトは、2つの整数パラメータを取ります。

struct Matrix {
    let rows: Int, columns: Int
    var grid: [Double]
    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        grid = Array(repeating: 0.0, count: rows * columns)
    }
    func indexIsValid(row: Int, column: Int) -> Bool {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }
    subscript(row: Int, column: Int) -> Double {
        get {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            return grid[(row * columns) + column]
        }
        set {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            grid[(row * columns) + column] = newValue
        }
    }
}

// Matrix初期化子に適切な行と列の数を渡すことにより、新しいインスタンスを構築できます。
var matrix = Matrix(rows: 2, columns: 2)

// 行列の値は、行と列の値をコンマで区切ってサブスクリプトに渡すことで設定できます。
matrix[0, 1] = 1.5
matrix[1, 0] = 3.2

// 行列の境界外にあるサブスクリプトにアクセスしようとすると、アサーションがトリガーされます。
let someValue = matrix[2, 2]
// This triggers an assert, because [2, 2] is outside of the matrix bounds.

タイプサブスクリプト

上記のインスタンスサブスクリプトは、特定のタイプのインスタンスで呼び出すサブスクリプトです。タイプそのもので呼び出されるサブスクリプトを定義することもできます。この種のサブスクリプトは、タイプサブスクリプトと呼ばれます。タイプのサブスクリプトは、subscriptキーワードの前にstaticキーワードを表記します。クラスは代わりにclassキーワードを使用して、サブクラスがそのサブスクリプトのスーパークラスの実装をオーバーライドできるようにすることができます。以下の例は、タイプのサブスクリプトを定義して呼び出す方法を示しています。

// enum のタイプサブスクリプト
enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
    static subscript(n: Int) -> Planet {
        return Planet(rawValue: n)!
    }
}
let mars = Planet[4]
print(mars)

// クラス のタイプサブスクリプト
class Planet {
    class subscript(n: Int) -> String {
        return "subscript class"
    }
}
let mars = Planet[4]
print(mars)

メソッド

メソッドは、特定の型に関連づけられた関数のことです。クラス、struct、および enum 型はすべて、特定のタイプのインスタンスを操作するための特定のタスクと機能をカプセル化するインスタンスメソッドを定義できます。クラス、struct、および enum 型は、型自体を関連付ける型メソッドも定義できます。タイプメソッドは、Objective-Cのクラスメソッドに似ています。

Objective-Cでは、メソッドを定義できるのはクラスだけです。Swiftでは、クラス、struct、またはenum いずれでもメソッドを定義することができます。

インスタンスメソッド

インスタンスメソッドは、特定のクラス、struct、または enum のインスタンスに属する関数です。これらは、インスタンスのプロパティにアクセスして変更するメソッドを提供するか、インスタンスの目的に関連する機能を提供することによって、それらのインスタンスの機能をサポートします。で説明したようにインスタンスメソッドは、機能とまったく同じ構文を持つ関数

インスタンスメソッドは、それが属するタイプの開始括弧と終了括弧内に記述します。インスタンスメソッドは、そのタイプの他のすべてのインスタンスメソッドとプロパティに暗黙的にアクセスできます。インスタンスメソッドは、それが属するタイプの特定のインスタンスでのみ呼び出すことができます。既存のインスタンスがなければ、単独で呼び出すことはできません。

// アクションが発生した回数をカウントする例
 class Counter {
    var count = 0
    func increment() {
        count += 1
    }
    func increment(by amount: Int) {
        count += amount
    }
    func reset() {
        count = 0
    }
}

// プロパティと同じドット構文でインスタンスメソッドを呼び出します。
let counter = Counter()
counter.increment()
// 関数パラメーターには、名前(関数の本体内で使用)と引数ラベル(関数を呼び出すときに使用)の両方を設定できます。
counter.increment(by: 5)
counter.reset()

セルフプロパティ

型のすべてのインスタンスにはと呼ばれる暗黙的なプロパティselfがあり、インスタンス自体とまったく同じです。selfプロパティを使用して、独自のインスタンスメソッド内で現在のインスタンスを参照します。

increment()上記の例のメソッドは、次のように書くことができます。

func increment() {
    self.count += 1
}

このルールの主な例外は、インスタンスメソッドのパラメーター名がそのインスタンスのプロパティと同じ名前である場合に発生します。この状況では、パラメーター名が優先され、より限定的なメソッドでプロパティを参照する必要があります。selfプロパティを使用して、パラメータ名とプロパティ名を区別します。

struct Point {
    var x = 0.0, y = 0.0
    func isToTheRightOf(x: Double) -> Bool {
        // self.xはプロパティとしての x
        // x は引数の名前としての x 
        // パラメータ名の x が優先される
        return self.x > x
    }
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOf(x: 1.0) {
    print("This point is to the right of the line where x == 1.0")
}

インスタンスメソッド内からの値型の変更

structとenum 型は値型です。デフォルトでは、そのインスタンスメソッド内から値型のプロパティを変更できません。

しかし、func の前にmutatingキーワードを配置することで、プロパティを変更できるようになります。

struct Point {
    var x = 0.0, y = 0.0
    // mutating キーワードを追加することにより、メソッドの中でプロパティを変更できるようになる
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        x += deltaX
        y += deltaY
    }
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// Prints "The point is now at (3.0, 4.0)"

mutating キーワードがないと、コンパイル時に、”Left side of mutating operator isn’t mutable: ‘self’ is immutable” エラーが発生する。

定数で定義された struct の変数プロパティでは、プロパティを変更できないため、変更メソッドを呼び出すことはできません。

mutating メソッド内での self への割り当て

mutating メソッドにより、完全に新しいインスタンスを暗黙的なselfプロパティに割り当てることができます。

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        self = Point(x: x + deltaX, y: y + deltaY)
    }
}

enum の mutating メソッドにより、暗黙的なselfパラメーターを同じ enum とは異なるケースに設定できます。

// この例では、トライステートスイッチのenumを定義しています。スイッチは、そのメソッドが呼び出されるたびにoff、3つの異なる電源状態(lowとhigh)の間をnext()により循環します。
enum TriStateSwitch {
    case off, low, high
    mutating func next() {
        switch self {
        case .off:
            self = .low
        case .low:
            self = .high
        case .high:
            self = .off
        }
    }
}
var ovenLight = TriStateSwitch.low
ovenLight.next()
// ovenLight is now equal to .high
ovenLight.next()
// ovenLight is now equal to .off

タイプメソッド

上記のインスタンスメソッドは、特定のタイプのインスタンスで呼び出すメソッドです。型自体で呼び出されるメソッドを定義することもできます。これらの種類のメソッドは、タイプメソッドと呼ばれます。メソッド名の前の func キーワードの前に static キーワードを記述することで、タイプメソッドであることを示します。クラスは代わりに class キーワードを使用して、サブクラスがそのメソッドのスーパークラスの実装をオーバーライドできるようにすることができます。

注意

Objective-Cでは、Objective-Cクラスに対してのみ型レベルのメソッドを定義できます。Swiftでは、すべてのクラス、struct、および enum に対してタイプレベルのメソッドを定義できます。各タイプメソッドは、サポートするタイプに明示的にスコープされます。

タイプメソッドは、インスタンスメソッドと同様に、ドット構文で呼び出されます。ただし、その型のインスタンスではなく、その型に対して呼び出します。

// SomeClassクラスから、タイプメソッドを呼び出す例
class SomeClass {
    class func someTypeMethod() {
        // type method implementation goes here
    }
}
SomeClass.someTypeMethod()
// ゲームのさまざまなレベルまたはステージでのプレーヤーの進行状況を追跡
struct LevelTracker {
    // タイププロパティ: 最高レベル
    static var highestUnlockedLevel = 1
    var currentLevel = 1
    // タイプメソッド unlock
    static func unlock(_ level: Int) {
        if level > highestUnlockedLevel { highestUnlockedLevel = level }
    }
    // タイプメソッド isUnlocked
    static func isUnlocked(_ level: Int) -> Bool {
        return level <= highestUnlockedLevel
    }

    @discardableResult
    mutating func advance(to level: Int) -> Bool {
        if LevelTracker.isUnlocked(level) {
            currentLevel = level
            return true
        } else {
            return false
        }
    }
}
// 個々のプレーヤーのゲームの進行状況を追跡、更新
class Player {
    var tracker = LevelTracker()
    let playerName: String
    func complete(level: Int) {
        LevelTracker.unlock(level + 1)
        tracker.advance(to: level + 1)
    }
    init(name: String) {
        playerName = name
    }
}
// プレーヤーがレベル1を完了するとどうなるかを確認
var player = Player(name: "Argyrios")
player.complete(level: 1)
print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
// Prints "highest unlocked level is now 2"
// ゲーム内のどのプレーヤーもまだロック解除していないレベル6に
// 移動しようとした場合
player = Player(name: "Beto")
if player.tracker.advance(to: 6) {
    print("player is now on level 6")
} else {
    print("level 6 has not yet been unlocked")
}
// Prints "level 6 has not yet been unlocked"

プロパティ

プロパティは、値を特定のクラス、構造、または列挙に関連付けます。格納されたプロパティはインスタンスの一部として定数値と変数値を格納しますが、計算されたプロパティは値を(格納するのではなく)計算します。計算されたプロパティは、クラス、構造、および列挙によって提供されます。格納されたプロパティは、クラスと構造体によってのみ提供されます。

格納および計算されたプロパティは、通常、特定のタイプのインスタンスに関連付けられています。ただし、プロパティをタイプ自体に関連付けることもできます。このようなプロパティはタイププロパティと呼ばれます。

さらに、プロパティオブザーバーを定義して、プロパティの値の変化を監視できます。これには、カスタムアクションで応答できます。プロパティオブザーバーは、自分で定義した格納済みプロパティに追加したり、サブクラスがスーパークラスから継承したプロパティに追加したりできます。

プロパティラッパーを使用して、複数のプロパティのゲッターとセッターでコードを再利用することもできます。

格納されたプロパティ

格納されたプロパティは、特定のクラスまたは構造のインスタンスの一部として格納される定数または変数です。格納されたプロパティは、var キーワードにより変数として格納されたプロパティまたは let キーワードにより定数の格納されたプロパティのいずれかです。

struct FixedLengthRange {
    var firstValue: Int    // 変更できる
    let length: Int     // 初期化後は変更できない
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// the range represents integer values 0, 1, and 2
rangeOfThreeItems.firstValue = 6
// the range now represents integer values 6, 7, and 8

定数の構造体インスタンスの格納されたプロパティ

構造体のインスタンスを作成し、そのインスタンスを定数に割り当てると、インスタンスが可変プロパティとして宣言されていても、インスタンスのプロパティを変更することはできません。

let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// rangeOfFourItems は定数として定義される
rangeOfFourItems.firstValue = 6
// rangeOfFourItems は定数のため、firstValue は変数として定義されているが、変更できない

遅延格納プロパティ

遅延格納ロパティは、その初期値が使用されている最初の時間まで計算されません。宣言の前に lazy 修飾子を記述することにより、遅延格納プロパティを示します。

注意

インスタンスの初期化が完了するまで初期値が取得されない可能性があるため、レイジープロパティは常に var 変数として宣言する必要があります。定数プロパティは、初期化が完了する前に常に値を持つ必要があるため、遅延として宣言することはできません。

遅延プロパティは、プロパティの初期値が、インスタンスの初期化が完了するまで値がわからない外部要因に依存している場合に役立ちます。または必要になるまで実行する必要のない、複雑で計算量の多いセットアップが必要な場合にも役立ちます。

以下の例では、遅延格納プロパティを使用して、複雑なクラスの不要な初期化を回避しています。この例では、DataImporterおよびDataManagerと呼ばれる2つのクラスを定義していますが、どちらも完全には表示されていません。

class DataImporter {
    // 外部ファイルからデータを取り込むが、初期化に時間がかかる
    var filename = "data.txt"
    // データ取得処理
}

class DataManager {
    lazy var importer = DataImporter()
    var data = [String]()
    // データ管理の実装部分
}

let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// この時点でDataImporter のインスタンスはまだ作成されていない

print(manager.importer.filename)
// importer が初めて使用されたので、この時点でインスタンスが作られる

注意

lazy修飾子でマークされたプロパティが複数のスレッドによって同時にアクセスされ、プロパティがまだ初期化されていない場合、プロパティが1回だけ初期化される保証はありません。

格納プロパティとインスタンス変数

Objective-Cの経験がある場合、クラスインスタンスの一部として値と参照を格納する2つの方法が提供されていることをご存知でしょう。プロパティに加えて、インスタンス変数をプロパティに格納されている値のバッキングストアとして使用できます。

Swiftはこれらの概念を単一のプロパティ宣言に統合します。Swiftプロパティには対応するインスタンス変数がなく、プロパティのバッキングストアには直接アクセスしません。このアプローチにより、さまざまなコンテキストで値にアクセスする方法についての混乱が回避され、プロパティの宣言が1つの明確なステートメントに簡略化されます。名前、タイプ、メモリ管理特性など、プロパティに関するすべての情報は、タイプの定義の一部として1つの場所で定義されます。

計算プロパティ

格納プロパティに加えて、クラス、構造体、および列挙体は、実際には値を格納しない計算プロパティを定義できます。代わりに、他のプロパティと値を間接的に取得および設定するためのゲッターとオプションのセッターを提供します。

// ポイントのx座標とy座標をカプセル化
struct Point {
    var x = 0.0, y = 0.0
}

// サイズをカプセル化
struct Size {
    var width = 0.0, height = 0.0
}

// 原点とサイズで長方形を定義
struct Rect {
    var origin = Point()
    var size = Size()
    // センター: 計算プロパティ
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}

// 新しいRect変数を作成
var square = Rect(origin: Point(x: 0.0, y: 0.0),
                  size: Size(width: 10.0, height: 10.0))
// 計算プロパティでセンターが計算される
let initialSquareCenter = square.center
// センターに新しい値を設定
square.center = Point(x: 15.0, y: 15.0)
// セッターにより原点プロパティが計算・設定される
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// Prints "square.origin is now at (10.0, 10.0)"

セッターの短縮宣言

struct AlternativeRect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set {
            // 既定の newValue が使用できる
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

ゲッターの短縮宣言

ゲッターの本体全体が単一の式である場合、ゲッターは暗黙的にその式を返します。

struct CompactRect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            // 暗黙的に Point を返すため、return が不要。
            Point(x: origin.x + (size.width / 2),
                  y: origin.y + (size.height / 2))
        }
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

読み取り専用の計算プロパティ

ゲッターはあるがセッターがない計算プロパティは、読み取り専用の計算プロパティと呼ばれます。読み取り専用の計算プロパティは常に値を返し、ドット構文を介してアクセスできますが、別の値に設定することはできません。

注意

読み取り専用の計算プロパティを含む計算プロパティは、値が固定されていないため、var キーワードを使用して変数プロパティとして宣言する必要があります。

getキーワードとその中括弧を削除すると、読み取り専用の計算プロパティの宣言を簡略化できます。

struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double {
        get {
              return width * height * depth
        }
    }
}

// 上記は、次のように getキーワードとその中括弧を削除し、
// 読み取り専用の計算プロパティの宣言を簡略化できます。
struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double {
        return width * height * depth
    }
}

プロパティオブザーバー

プロパティオブザーバーは、プロパティの値の変化を観察して応答します。プロパティオブザーバーは、新しい値がプロパティの現在の値と同じであっても、プロパティの値が設定されるたびに呼び出されます。

プロパティオブザーバーは、遅延格納プロパティを除き、定義した格納プロパティに追加できます。また、サブクラス内のプロパティをオーバーライドすることにより、プロパティオブザーバーを継承されたプロパティ(格納または計算されたもの)に追加できます。オーバーライドされていない計算されたプロパティのプロパティオブザーバーを定義する必要はありません。計算されたプロパティのセッターで値の変化を観察して応答できるためです。プロパティのオーバーライドはオーバーライドで説明します。

プロパティでこれらのオブザーバーのいずれかまたは両方を定義することができます。

  • willSet 値が格納される直前に呼び出されます。
  • didSet 新しい値が格納された直後に呼び出されます。

willSetオブザーバーを実装すると、新しいプロパティ値が定数パラメーターとして渡されます。willSet実装の一部として、このパラメーターの名前を指定できます。実装内でパラメーター名と括弧を記述しない場合、パラメーターはのデフォルトのパラメーター名 newValue で使用可能になります。

同様に、didSetオブザーバーを実装すると、古いプロパティ値を含む定数パラメーターが渡されます。パラメータに名前を付けるか、デフォルトのパラメータ名oldValueを使用できます。didSetオブザーバ内のプロパティに値を割り当てると、新しい値に置き換わります。

注意

スーパークラスプロパティのwillSetおよびdidSetオブザーバーは、スーパークラス初期化子が呼び出された後、サブクラス初期化子でプロパティが設定されたときに呼び出されます。スーパークラスのイニシャライザーが呼び出される前に、クラスが独自のプロパティを設定している間は呼び出されません。

イニシャライザーデリゲーションの詳細については、「値型のイニシャライザーデリゲーションおよび「クラス型のイニシャライザーデリゲーションを参照してください。

ここでの例だwillSetdidSet、アクションでは。次の例では、と呼ばれる新しいクラスを定義しStepCounterます。これは、人が歩くときに行う歩数の合計を追跡します。このクラスは、歩数計やその他の歩数計からの入力データと一緒に使用して、日常業務中の人の運動を追跡することができます。

// 人が歩くときに行う歩数の合計を追跡する例
class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("About to set totalSteps to \(newTotalSteps)")
        }
        didSet {
            // oldValue はデフォルトのパラメータ名
            if totalSteps > oldValue  {
                print("Added \(totalSteps - oldValue) steps")
            }
        }
    }
}

let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// willSet が呼ばれる: About to set totalSteps to 200
// didSet が呼ばれる: Added 200 steps

注意

オブザーバーを持つプロパティを入出力パラメーターとして関数に渡すと、常にオブザーバーwillSetdidSetオブザーバーが呼び出されます。これは、入出力パラメーターのコピーインコピーアウトメモリモデルが原因です。値は常に、関数の最後でプロパティに書き戻されます。イン・アウトパラメータの動作の詳細な議論については、イン・アウトパラメータを参照。

プロパティラッパー

プロパティラッパーは、プロパティの格納方法を管理するコードとプロパティを定義するコードの間の分離層を追加します。たとえば、スレッドセーフチェックを提供するプロパティや、基になるデータをデータベースに格納するプロパティがある場合は、すべてのプロパティにそのコードを記述する必要があります。プロパティラッパーを使用する場合、ラッパーを定義するときに管理コードを1回記述し、それを複数のプロパティに適用することでその管理コードを再利用します。

プロパティラッパー を定義するには、プロパティwrappedValueを定義する struct、enum、またはクラスを作成します。以下のコードでは、このTwelveOrLess構造により、ラップする値に常に12以下の数が含まれることが保証します。より大きな数を格納するように要求すると、代わりに12が格納されます。

@propertyWrapper
struct TwelveOrLess {
    private var number: Int
    init() { self.number = 0 }
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}

注意

上記の例の宣言は、変数numberをprivateとしてマークします。これにより、TwelveOrLessの実装でのみnumberが使用されます。他の場所で記述されたコードは、wrappedValueのゲッターとセッターを使用して値にアクセスし、直接numberを使用することはできません。privateの詳細については、「アクセス制御」を参照してください。

// プロパティーラッパーの例
// プロパティーラッパー 名 "@TwelveOrLess"をプロパティの前に記述する
struct SmallRectangle {@TwelveOrLess 
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

var rectangle = SmallRectangle()
print(rectangle.height)
// Prints "0"

rectangle.height = 10
print(rectangle.height)
// Prints "10"

rectangle.height = 24
print(rectangle.height)
// Prints "12"

// プロパティラッパーを使用しないと、SmallRectangleは次のようになります。
struct SmallRectangle {
    private var _height = TwelveOrLess()
    private var _width = TwelveOrLess()
    var height: Int {
        get { return _height.wrappedValue }
        set { _height.wrappedValue = newValue }
    }
    var width: Int {
        get { return _width.wrappedValue }
        set { _width.wrappedValue = newValue }
    }
}

ラップされたプロパティの初期値の設定

上記の例のコードは、TwelveOrLessの定義で初期値 number を指定することにより、ラップされたプロパティの初期値を設定します。このプロパティラッパーを使用するコードでは、ラップされたプロパティに別の初期値を指定できません。たとえば、「SmallRectangle」は、heightまたはwidth の初期値を与え得ることができません。初期値の設定やその他のカスタマイズをサポートするには、プロパティラッパーでイニシャライザーを追加する必要があります。

// ラップされた初期値を設定する例
@propertyWrapper
struct SmallNumber {
    private var maximum: Int
    private var number: Int

    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, maximum) }
    }

    init() {
        maximum = 12
        number = 0
    }
    // 追加したイニシャライザー
    init(wrappedValue: Int) {
        maximum = 12
        number = min(wrappedValue, maximum)
    }
    // 追加したイニシャライザー
    init(wrappedValue: Int, maximum: Int) {
        self.maximum = maximum
        number = min(wrappedValue, maximum)
    }
}

初期化とイニシャライザーの構文の詳細については、初期化を参照。

プロパティにラッパーを適用し、初期値を指定しない場合、Swiftはinit()イニシャライザーを使用してラッパーを設定します。例えば:

struct ZeroRectangle {
    @SmallNumber var height: Int
    @SmallNumber var width: Int
}

var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// Prints "0 0"

heightとwidthをラップするインスタンスSmallNumberは、SmallNumber()を呼び出すことによって作成されます。イニシャライザーのコードは、ゼロと12のデフォルト値を使用して、初期ラップ値と初期最大値を設定します。プロパティラッパーは、前述のSmallRectangleで使用したTwelveOrLess例のように、すべての初期値を提供します。その例とは異なり、SmallNumberは、プロパティの宣言の一部としてこれらの初期値の書き込みもサポートしています。

プロパティの初期値を指定すると、Swiftはinit(wrappedValue:)イニシャライザーを使用してラッパーを設定します。例えば:

struct UnitRectangle {
    @SmallNumber var height: Int = 1
    @SmallNumber var width: Int = 1
}

var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// Prints "1 1"

カスタム属性の後に括弧で引数を記述すると、Swiftはそれらの引数を受け入れるイニシャライザを使用してラッパーを設定します。たとえば、初期値と最大値を指定すると、Swiftはinit(wrappedValue:maximum:)イニシャライザーを使用します。

struct NarrowRectangle {
    @SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
    @SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
}

var narrowRectangle = NarrowRectangle()
print(narrowRectangle.height, narrowRectangle.width)
// Prints "2 3"

narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// Prints "5 4"

プロパティラッパーに引数を含めることで、ラッパーに初期状態を設定したり、ラッパーの作成時に他のオプションをラッパーに渡したりできます。この構文は、プロパティラッパーを使用する最も一般的な方法です。属性に必要な引数はすべて提供でき、それらはイニシャライザーに渡されます。

プロパティラッパー引数を含める場合、割り当てを使用して初期値を指定することもできます。Swiftは割り当てをwrappedValue引数のように扱い、指定した引数を受け入れるイニシャライザを使用します。例えば:

struct MixedRectangle {
    @SmallNumber var height: Int = 1
    @SmallNumber(maximum: 9) var width: Int = 2
}

var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// Prints "1"

mixedRectangle.height = 20
print(mixedRectangle.height)
// Prints "12"

heightをラップするSmallNumberインスタンスは、SmallNumber(wrappedValue: 1) を呼び出すことによって作成されます。これは、デフォルトの最大値12を使用します。widthをラップするインスタンスは、SmallNumber(wrappedValue: 2, maximum: 9)を呼び出すことによって作成されます。

プロパティラッパーから値を投影する

ラップされた値に加えて、プロパティラッパーは投影された値を定義することで追加機能を公開できます。たとえば、データベースへのアクセスを管理するプロパティラッパーは、flushDatabaseConnection()メソッドに投影された値を公開できます。投影された値の名前は、ドル記号($)で始まることを除いて、ラップされた値と同じです。コードは$で始まるプロパティを定義できないため、投影された値は定義したプロパティに干渉することはありません。

上記のSmallNumberの例では、プロパティを大きすぎる数値に設定しようとすると、プロパティラッパーが数値を調整してから保存します。以下のコードは、プロパティラッパーが新しい値を格納する前にプロパティの新しい値を調整したかどうかを追跡するために、SmallNumber構造体にprojectedValueプロパティを追加します。

@propertyWrapper
struct SmallNumber {
    private var number: Int
    var projectedValue: Bool
    init() {
        self.number = 0
        self.projectedValue = false
    }
    var wrappedValue: Int {
        get { return number }
        set {
            if newValue > 12 {
                number = 12
                projectedValue = true
            } else {
                number = newValue
                projectedValue = false
            }
        }
    }
}
struct SomeStructure {
    @SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()

someStructure.someNumber = 4
print(someStructure.$someNumber)
// Prints "false"

someStructure.someNumber = 55
print(someStructure.$someNumber)
// Prints "true"

プロパティラッパーは、投影された値として任意の型の値を返すことができます。この例では、プロパティラッパーは、数値が調整されたかどうかにかかわらず、1つの情報のみを公開するため、そのブール値を予測値として公開します。より多くの情報を公開する必要があるラッパーは、他のデータ型selfのインスタンスを返すことができます。または、ラッパーのインスタンスを投影された値として公開するために戻ることができます。

プロパティゲッターやインスタンスメソッドなど、タイプの一部であるコードから投影されたにアクセスする場合、他のプロパティにアクセスする場合と同様に、プロパティ名の前のselfを省略できます。次の例のコードは、ラッパーのheightwidth を投影された値 $heightおよび$width として参照しています。

enum Size {
    case small, large
}

struct SizedRectangle {
    @SmallNumber var height: Int
    @SmallNumber var width: Int

    mutating func resize(to size: Size) -> Bool {
        switch size {
        case .small:
            height = 10
            width = 20
        case .large:
            height = 100
            width = 100
        }
        return $height || $width
    }
}

プロパティラッパーの構文は、getterとsetterを持つプロパティの単なるシンタックスシュガーであるため、他のプロパティへのアクセスと同じように hightとwidth にアクセスします。たとえば、コードのresize(to:) はheightwidth にプロパティラッパーを使用しアクセスします。resize(to:) を呼び出す場合、.large の switch case は長方形の高さと幅を100 に設定します。ラッパーはこれらのプロパティの値が12を超えないようにし、投影値を true に設定して、値を調整したことを記録します。終わりにreturn文で $height と $width をチェックし、そしてプロパティラッパーがどちらかを調整したかどうかを決定します。

グローバル変数とローカル変数

プロパティの計算と監視に関する上記の機能は、グローバル変数ローカル変数でも使用できます。グローバル変数は、関数、メソッド、クロージャー、またはタイプコンテキストの外部で定義される変数です。ローカル変数は、関数、メソッド、またはクロージャコンテキスト内で定義される変数です。

前の章のグローバル変数とローカル変数はすべて格納された変数です。格納されたプロパティのような格納された変数は、特定のタイプの値のストレージを提供し、その値を設定および取得できます。

計算する変数を定義し、保存された変数のオブザーバーをグローバルスコープまたはローカルスコープで定義することもできます。計算する変数は、値を格納するのではなく計算し、計算するプロパティと同じ方法で書き込まれます。

注意

グローバル定数と変数は、遅延格納プロパティと同様に、常に遅延計算さます。遅延格納プロパティとは異なり、グローバル定数と変数はlazy修飾子でマークする必要はありません。

ローカル定数と変数が遅延計算されることはありません。

タイププロパティ

インスタンスプロパティは、特定のタイプのインスタンスに属するプロパティです。そのタイプの新しいインスタンスを作成するたびに、他のインスタンスとは別に、独自のプロパティ値のセットがあります。

また、そのタイプの1つのインスタンスではなく、タイプ自体に属するプロパティを定義することもできます。作成したそのタイプのインスタンスの数に関係なく、これらのプロパティのコピーは1つしかありません。これらの種類のプロパティは、タイププロパティと呼ばれます

タイププロパティは、すべてのインスタンスが使用できる定数プロパティ(Cの静的定数など)や、すべてにグローバルな値を格納する変数プロパティなど、特定のタイプのすべてのインスタンスに共通の値を定義するのに役立ちます。(Cの静的変数に似ている)。

格納するタイププロパティは、変数または定数にすることができます。計算するタイププロパティは、計算するインスタンスプロパティと同じように、常に変数プロパティとして宣言されます。

注意

格納されたインスタンスプロパティとは異なり、格納されたタイププロパティには常にデフォルト値を与える必要があります。これは、型自体に、初期化時に格納されたタイププロパティに値を割り当てることができる初期化子がないためです。

格納するタイププロパティは、最初のアクセス時に遅延初期化されます。これらは、複数のスレッドから同時にアクセスされた場合でも、1回だけ初期化されることが保証されており、lazy修飾子でマークする必要はありません。

タイププロパティの構文

CとObjective-Cでは、タイプに関連付けられた静的定数と変数をグローバル静的変数として定義します。ただし、Swiftでは、タイププロパティはタイプの定義の一部として、タイプの外側の中括弧内に記述され、各タイププロパティは、サポートするタイプに明示的にスコープされます。

staticキーワードを使用してタイププロパティを定義します。クラスタイプの計算タイププロパティの場合、class代わりにキーワードを使用して、サブクラスがスーパークラスの実装をオーバーライドできるようにすることができます。以下の例は、格納および計算されたタイププロパティの構文を示しています。

struct SomeStructure {
    // 格納するタイププロパティ
    static var storedTypeProperty = "Some value."
    // 計算するタイププロパティ
    static var computedTypeProperty: Int {
        return 1
    }
}
enum SomeEnumeration {
    // 格納するタイププロパティ
    static var storedTypeProperty = "Some value."
    // 計算するタイププロパティ
    static var computedTypeProperty: Int {
        return 6
    }
}
class SomeClass {
    // 格納するタイププロパティ
    static var storedTypeProperty = "Some value."
    // 計算するタイププロパティ
    static var computedTypeProperty: Int {
        return 27
    }
    // オーバーライドできる計算するタイププロパティ
    class var overrideableComputedTypeProperty: Int {
        return 107
    }
}

注意

上記の計算タイププロパティの例は、読み取り専用の計算タイププロパティの場合ですが、計算インスタンスプロパティと同じ構文で読み取り/書き込み計算タイププロパティを定義することもできます。

タイププロパティのクエリと設定

タイププロパティは、インスタンスプロパティと同様にドット構文で、クエリ・セットされます。ただし、タイププロパティは、そのタイプのインスタンスではなく、タイプで照会および設定されます。例えば:

// 格納するタイププロパティへのアクセス
print(SomeStructure.storedTypeProperty)
// Prints "Some value."
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// Prints "Another value."

// 計算するタイププロパティへのアクセス
print(SomeEnumeration.computedTypeProperty)
// Prints "6"
print(SomeClass.computedTypeProperty)
// Prints "27"

以下の例では、いくつかのオーディオチャネルのオーディオレベルメーターをモデル化する構造の一部として、2つの格納されたタイププロパティを使用しています。各チャンネルには、整数のオーディオレベル010があります。

次の図は、これらのオーディオチャネルの2つを組み合わせてステレオオーディオレベルメーターをモデル化する方法を示しています。チャネルのオーディオレベルが0の場合、そのチャネルのライトはどれも点灯しません。オーディオレベルが10の場合、そのチャネルのすべてのライトが点灯します。この図では、左チャネルの現在のレベルは9であり、右チャネルの現在のレベルは7です。../_images/staticPropertiesVUMeter_2x.png

上記のオーディオチャネルは、AudioChannel構造のインスタンスによって表されます。

struct AudioChannel {
    static let thresholdLevel = 10
    static var maxInputLevelForAllChannels = 0
    var currentLevel: Int = 0 {
        didSet {
            if currentLevel > AudioChannel.thresholdLevel {
                // cap the new audio level to the threshold level
                currentLevel = AudioChannel.thresholdLevel
            }
            if currentLevel > AudioChannel.maxInputLevelForAllChannels {
                // store this as the new overall maximum input level
                AudioChannel.maxInputLevelForAllChannels = currentLevel
            }
        }
    }
}

AudioChannelは、その機能をサポートするために2つの保存されたタイプのプロパティを定義します。最初のthresholdLevelは、オーディオレベルが取り得る最大しきい値を定義します。thresholdLevelは、すべてのAudioChannelインスタンスで定数値10になります。オーディオ信号がより大きい値で受信された場合、このしきい値10に制限されます。

2番目のタイプのプロパティは、maxInputLevelForAllChannelsと呼ばれる変数格納プロパティです。これは AudioChannelインスタンスによって受信された最大入力値を追跡します。最初は初期値0です。

AudioChannel は、格納されたインスタンスのプロパティcurrentLevelを定義します。これは、チャネルの現在のオーディオレベルを 0〜10のスケールで表します。

currentLevelプロパティには、currentLevel が設定されるたびに currentLevelをチェックするプロパティオブザーバdidSetがあります。このオブザーバーは次の2つのチェックを実行します。

  • currentLevelの新しい値が許可された値thresholdLevelより大きい場合、プロパティオブザーバーはcurrentLevelを上限のthresholdLevelに設定します。
  • currentLevelの新しい値が、任意の AudioChannelインスタンスにより以前受信された値よりも大きいの場合、プロパティオブザーバは、maxInputLevelForAllChannels タイププロパティに新しいcurrentLevelの値を格納します。

注意

これら2つのチェックの最初のチェックでは、didSetオブザーバーはcurrentLevel異なる値に設定します。ただし、これによってオブザーバーが再度呼び出されることはありません。

ステレオサウンドシステムのオーディオレベルを表すために、あなたは2つの新しいオーディオチャンネル leftChannel と rightChannelをAudioChannel から作成することができます。

var leftChannel = AudioChannel()
var rightChannel = AudioChannel()

あなたがcurrentLevelにチャンネルを7に設定した場合、あなたはそれを見ることができるmaxInputLevelForAllChannelsタイプのプロパティが 7 に等しくなるように更新されます:

leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// Prints "7"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "7"

あなたがcurrentLevelにチャンネルを11に設定しようとした場合、あなたは右チャンネルのcurrentLevelプロパティが、最大値に 10 に制限され、maxInputLevelForAllChannels タイププロパティが 10 に等しくなるように更新されます:

rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// Prints "10"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "10"

構造体とクラス

他のプログラミング言語とは異なり、Swiftでは、カスタム構造体とクラス用にインターフェースとインプリメンテーションのための個別のファイルを作成する必要はありません。Swiftでは、一つのファイルで構造体またはクラスを定義すると、そのクラスまたは構造体への外部インターフェイスが他のコードで自動的に使用できるようになります。

注意

クラスのインスタンスは、伝統的にオブジェクトとして知られています。しかし、Swiftの構造体とクラスは、他の言語に比べてはるかに近い機能を持ちます。この章では、構造体とクラスのインスタンスに適用でする機能について説明します。以下では、インスタンスは、一般的用語として使用します。

構造体とクラスの比較

Swiftの構造体とクラスには次の多くの共通点があります。

  • 値を格納するプロパティを定義する
  • 機能を提供するメソッドを定義する
  • サブスクリプト文法を使用して値へのアクセスを提供するサブスクリプトを定義する
  • 初期化を定義して初期状態を設定する
  • デフォルトの実装を超えて機能を拡張するために拡張される
  • プロトコルに準拠し、特定の種類の標準機能を提供する

クラスには、構造体にはない追加機能があります。

  • 継承により、あるクラスが別のクラスの特性を継承できるようになります。
  • 型キャストを使用すると、実行時にクラスインスタンスの型を確認およびインタープリットできます。
  • デイニシャライザを使用すると、クラスのインスタンスが、割り当てたリソースを解放できます。
  • 参照カウントは、クラスインスタンスへの複数の参照を可能にします。

クラスがサポートする追加機能には、複雑さが増すという代償があります。一般的なガイドラインとして、推論しやすい構造体を優先し、必要に応じてクラスを使用します。実際、あなたが定義するカスタムデータ型のほとんどは、構造体および列挙型になるでしょう。

定義構文

struct SomeStructure {
    // 構造体定義
}

class SomeClass {
    // クラス定義
}

// 例
struct Resolution {
    var width = 0     // プロパティ
    var height = 0     // プロパティ
}

class VideoMode {
    var resolution = Resolution()      // プロパティ
    var interlaced = false     // プロパティ
    var frameRate = 0.0     // プロパティ
    var name: String?     // プロパティ
}

注意

構造体またはクラス名は、UpperCamelCase を使用します。プロパティ名とメソッド名は、lowerCamelCaseを使用して、型名と区別します。

構造体とクラスのインスタンス

Resolution構造体の定義とVideoModeクラス定義は、形だけを定義します。それら自体は、特定の解像度またはビデオモードを記述していません。そのためには、構造体またはクラスのインスタンスを作成する必要があります。

// インスタンスの作成
let someResolution = Resolution()
let someVideoMode = VideoMode()

プロパティへのアクセス

ドット構文を使用してインスタンスのプロパティにアクセスできます

// プロパティへのアクセス例
print(someResolution.width)

// サブプロパティへのアクセス例
someVideoMode.resolution.width = 1280
print(someVideoMode.resolution.width)

構造型のメンバーのイニシャライザー

すべての構造体は、自動的に生成されるメンバーのイニシャライザーができます。

let vga = Resolution(width: 640, height: 480)

構造体とは異なり、クラスインスタンスはデフォルトのメンバーのイニシャライザーはありません。

構造体と enum は値型

Swiftのすべての基本型(整数、浮動小数点数、ブール値、文字列、配列、および辞書)は値型であり、構造体として実装されています。

すべての構造体と enum は、値型です。つまり、構造体と enum のインスタンス、およびプロパティは、コード内で渡されるときに常にコピーされます。

注意

配列、辞書、文字列などの標準ライブラリによって定義されたコレクションは、値型ですが、コピーのコストを最適化されており、すぐにコピーを作成せずに、元のインスタンスとコピーの間で要素のメモリを共有します。コレクションのコピーの1つが変更された場合、要素は変更の直前にコピーされます。このため、コピーがすぐに行われたかのように見えます。

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048

この時、プロパティはコピーされます。../_images/sharedStateStruct_2x.png

クラスは参照型です

値型とは異なり、参照型は、変数または定数に割り当てられたとき、または関数に渡されたときにコピーされません。コピーではなく、同じ既存のインスタンスへの参照が使用されます。

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

クラスは参照型であり、この場合、下の図に示すように、同じ単一インスタンスの2つの異なる名前にすぎません。../_images/sharedStateClass_2x.png

この例は、参照型が推論するのがいかに難しいかについても示しています。プログラムのコードに遠く離れていた場合、ビデオモードが変更されていることを見つけることは困難である可能性があります。値の型の方が簡単に推論できます。

アイデンティティーオペレーター

2つの定数または変数がクラスのまったく同じインスタンスを参照しているかどうかを確認する 2つの アイデンティティオペレーターが用意されています。

  • === 同じ
  • !== 同一ではない
if tenEighty === alsoTenEighty {
    print("tenEightyとalsoTenEightyは、同じインスタンス")
}

独自のカスタム構造体とクラスを定義する場合、2つのインスタンスが等しいと見なされるものを決定するのはユーザーの責任です。==and !=演算子の独自の実装を定義するプロセスについては、「等価演算子」で説明しています。

ポインタ

C、C ++、またはObjective-Cの経験がある場合は、これらの言語がポインターを使用してメモリ内のアドレスを参照していることを知っているかもしれません。ある参照型のインスタンスを参照するSwift定数または変数は、Cのポインターに似ていますが、メモリ内のアドレスへの直接のポインターではなく、アスタリスク(*)を記述する必要はありません。代わりに、これらの参照はSwiftの他の定数または変数と同様に定義されます。標準ライブラリは、ポインタと直接対話する必要がある場合に使用できるポインタとバッファタイプを提供します。「手動メモリ管理」を参照してください。

Enumeration

構文

enum SomeEnumeration {
    // enumeration definition goes here
}

// 例
enum CompassPoint {
    case north
    case south
    case east
    case west
}

// 注意
// Cなどと異なり、デフォルトで整数値は設定されない。代わりに、明示的に定義されたCompassPoint型

// カンマで区切って、複数のケースを1行に表示できます。
enum Planet {
    case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}

// enum の名前は大文字で始め、単数形で表記すること

var directionToHead = CompassPoint.west
// directionToHeadの型は、CompassPoint型として推測できる
// したがって、値を設定するときに型を省略できます。
directionToHead = .east

Switchステートメントを使用した列挙値のマッチング

directionToHead = .south
switch directionToHead {
case .north:
    print("Lots of planets have a north")
case .south:
    print("Watch out for penguins")
case .east:
    print("Where the sun rises")
case .west:
    print("Where the skies are blue")
}
// Prints "Watch out for penguins"

enum caseの列挙

// CaseIterable 型を指定することにより、allCases で全てのケースを列挙できる
enum Beverage: CaseIterable {
    case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// Prints "3 beverages available"

for beverage in Beverage.allCases {
    print(beverage)
}
// coffee
// tea
// juice

関連する値

UPCバーコードは4つの整数のタプル、QRコードバーコードを任意の長さの文字列として保存すると便利です。

// UPCバーコードとQRコードバーコードの両方を次のように表すことができる
enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}

var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")

switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
    print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
    print("QR code: \(productCode).")
}
// Prints "QR code: ABCDEFGHIJKLMNOP."

Raw Value

// Raw Value の設定方法
enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

Raw Valueは、文字列、文字、または整数または浮動小数点型のいずれかで、enum 宣言内で一意である必要があります。

暗黙的に割り当てられた Raw Value

整数または文字列の未加工値を格納する列挙型を使用している場合、各ケースに未加工値を明示的に割り当てないと、自動的に値を割り当てます。

たとえば、生の値に整数が使用されている場合、各ケースの暗黙的な値は前のケースよりも1つ多くなります。最初のケースに値が設定されていない場合、その値は0です。

以下の列挙はPlanet、太陽からの各惑星の順序を表す整数の生の値を使用して、以前の列挙を改良したものです。

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
// この場合、Planet.venusは2が割り当てられます。

enum CompassPoint: String {
    case north, south, east, west
}
// この場合、CompassPoint.south は "south"が割り当てられます。

let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3

let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"

Raw Value からの初期化

let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.uranus
// すべての可能な値が一致する惑星を見つけるわけではありません。このため、Raw Value の初期化子は常にオプショナルの Enum型のケースを返します。

// 例えば、Raw Value が 11 の惑星は nil が返る
let positionToFind = 11
if let somePlanet = Planet(rawValue: positionToFind) {
    switch somePlanet {
    case .earth:
        print("Mostly harmless")
    default:
        print("Not a safe place for humans")
    }
} else {
    print("There isn't a planet at position \(positionToFind)")
}
// Prints "There isn't a planet at position 11"

再帰的 enum

indirectにより、再帰的な enum を表すことができる。

// 単純な算術式を表す再帰的 enum
enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

// すべてのケースに対して indirect enum を宣言することもできる
indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}

// (5 + 4) * 2 を再帰的 enum で表した例
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

// 評価する関数の例
func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
    case let .number(value):
        return value
    case let .addition(left, right):
        return evaluate(left) + evaluate(right)
    case let .multiplication(left, right):
        return evaluate(left) * evaluate(right)
    }
}

print(evaluate(product))
// Prints "18"

この関数は、関連付けられた値を返すだけで、単純な数値を評価します。左側の式を評価し、右側の式を評価してから、それらを加算または乗算することにより、加算または乗算を評価します。

クロージャー

クロージャーの例

        // 例1
        // クロージャー1
        let add = {(val1: Int, val2: Int) -> Int in
            return val1 + val2
        }
        
        // クロージャー2
        let sub = {(val1: Int, val2: Int) -> Int in
            return val1 - val2
        }
        
        func exec(closure: (Int, Int) -> Int) {
            print(closure(3, 2))
        }

        // クロージャーを渡す
        exec( closure: add )
        exec( closure: sub )

        // 例2
        // クロージャー3
        let percentString = {(val: Double) -> String in
            return String(val * 100) + "%"
        }
        
        // label.text = String(divide(16,4))
        print(percentString(0.5))
        
        // 例3
        let completionClosure = {(val: Double) -> String in
            return String(val) + " Sec passed."
        }
        
        func bigTask(completion: (Double) -> (String)) {
            sleep(3)
            let endMessage = completion(3)
            print(endMessage)
        }
        // bigTask 実行
        bigTask(completion: completionClosure)

クロージャーの例2

        var closure1 = { (num1: Int, num2: Int) -> Int in return num1 + num2 }

        // 型推論による省略
        var closure2 = { (num1: Int, num2: Int) in return num1 + num2 }
        
        // 単文の場合は return が省略できる
        var closure3 = { (num1: Int, num2: Int) in num1 + num2 }

        // さらに型推論による省略
        var closure4 = { 1000 + 4 }
        
        // クロージャーをインライン実行
        var x = {
            1000 + 5
        }()
この例だと、まだ意味が推測できるが、次のようなコードを書かれると、パッと見た目に意味がわからず、読みづらい。
    /// - Tag: MLModelSetup
    lazy var classificationRequest: VNCoreMLRequest = {
        do {
            let config = MLModelConfiguration()
            config.computeUnits = .all
            //          let model = try VNCoreMLModel(for: CatDog100(configuration: config).model)
            let model1 = try SeaOfCloudClassifier1(configuration: config).model
            let model = try VNCoreMLModel(for: model1)
            
            let request = VNCoreMLRequest(model: model, completionHandler: { [weak self] request, error in
                self?.processClassifications(for: request as! VNCoreMLRequest, error: error)
            })
            request.imageCropAndScaleOption = .centerCrop
            return request
        } catch {
            fatalError("Failed to load Vision ML model: \(error)")
        }
    }()

以下は、Swift Programming Language 5.2 より。

クロージャーは、コード内で受け渡し、使用できる機能の自己完結型ブロックです。Swiftのクロージャーは、CおよびObjective-Cのブロックや、他のプログラミング言語のラムダに似ています。

クロージャーは次の3つの形式のいずれかを取ります。

  • グローバル関数は、名前があり、値をキャプチャしないクロージャです。
  • ネストされた関数は、名前を持つクロージャーであり、内なる関数から値を取り込むことができます。
  • クロージャ式は、軽量の構文で記述された名前のないクロージャであり、それを取り囲むコンテキストから値をキャプチャできます

クロージャ式の構文

クロージャ式の構文は、次の一般的な形式を持っています。inキーワードによってクロージャーの本体が始まります。

{ (parameters) -> return type in
    statements
}

次は、sorted(by:)メソッドをクロージャー式で簡潔に表現した例に説明します。

// sorted メソッド
// Swiftの標準ライブラリは、sorted(by:)メソッドを提供します。
       let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
        
// 2要素の大小比較をする関数表記
func backward(_ s1: String, _ s2: String) -> Bool {
   return s1 > s2
}
let r1 = names.sorted(by: backward)
print(r1)
        
// これをクロージャー式で表すと次のように書ける
let r2 = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})
print(r2)
        
// さらに型を推論できるので、型を省略する
let r3 = names.sorted(by: { s1, s2 in return s1 > s2 } )
print(r3)

// $0, $1 を使い、引数名を省略できる
let r4 = names.sorted(by: {$0 > $1 })
print(r4)
        
// さらに、Operator Method により、ここまで簡素化できる
let r5 = names.sorted(by: > )
print(r5)

Trailing クロージャー

// 次のクロージャーを Trailing クロージャーへ変換する
let r4 = names.sorted(by: {$0 > $1 })
print(r4)

// クロージャ式が関数またはメソッドの唯一の引数の場合、括弧 () を省略できます。

 // Trailing クロージャー
let r6 = names.sorted{$0 > $1 }
print(r6)

値のキャプチャ

クロージャは、それが定義されている周囲のコンテキストから定数と変数をキャプチャできます。その後、定数と変数を定義した元のスコープが存在しなくても、クロージャーはその本体内からこれらの定数と変数の値を参照および変更できます。

Swiftでは、値をキャプチャできる最も単純なクロージャの形式は、別の関数の本体内に記述されたネストされた関数です。ネストされた関数は、その外部関数の引数をキャプチャでき、外部関数内で定義されている定数と変数もキャプチャできます。

これは、makeIncrementerという関数の例です。これには、incrementerというネストされた関数が含まれています。ネストされたincrementer()関数は、周囲のコンテキストから2つの値runningTotalとamountを取得します。これらの値を捕捉した後、クロージャーincrementerによってrunningTotalが返される。

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

let incrementByTen = makeIncrementer(forIncrement: 10)

この例では、呼び出されるたびに変数に10追加されるインクリメンター関数を参照するために呼び出される定数を設定します。

incrementByTen()
// 10
incrementByTen()
// 20
incrementByTen()
// 30

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// 7

incrementByTen を再度呼び出すと、その runningTotal変数がインクリメントされるが、incrementBySevenによりキャプチャされた変数には影響しません

incrementByTen()
// 40

注意

クラスインスタンスのプロパティにクロージャーを割り当て、クロージャーがインスタンスまたはそのメンバーを参照することによってそのインスタンスをキャプチャする場合、クロージャーとインスタンスの間に強い参照サイクルが作成されます。Swiftはキャプチャリストを使用して、これらの強力な参照サイクルを壊します

クロージャは参照型です

上記の例ではincrementBySevenincrementByTenは定数ですが、これらの定数が参照するクロージャrunningTotalは、キャプチャした変数をインクリメントできます。これは、関数とクロージャが参照型であるためです。

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// returns a value of 50
incrementByTen()
// returns a value of 60

クロージャーのエスケープ

関数の外部で定義されている変数にクロージャーを格納する場合、エスケープが必要になります。

非同期操作を開始する多くの関数は、完了ハンドラーとしてクロージャー引数を取ります。関数は、操作の開始後に戻りますが、操作が完了するまでクロージャーは呼び出されません。この場合、関数の外側でクロージャが格納されるため、クロージャーはエスケープする必要があります。

// 実行部
        let instance = SomeClass()
        instance.doSomething()
        print(instance.x)
        // Prints "200"

        completionHandlers.first?()
        print(instance.x)
        // Prints "100"

var completionHandlers: [() -> Void] = []

class SomeClass {
    var x = 10
    func doSomething() {
        // エスケープクロージャーは、明示的にselfを参照する必要があります。
        escapingClosure { self.x = 100 }
        // ノンエスケープクロージャーは、暗黙的にselfに参照できます。
        nonescapingClosure { x = 200 }
    }
    
    // クロージャーの外側の completionHandlers に追加する場合
    // エスケープしないとコンパイル時エラーになる
    func escapingClosure(completionHandler: @escaping () -> Void) {
        completionHandlers.append(completionHandler)
    }

    func nonescapingClosure(closure: () -> Void) {
        closure()
    }
}

オートクロージャー

autoclosureは、明示的なクロージャーの代わりに通常の式を記述することで、関数のパラメーターの中括弧を省略できます。

オートクロージャーを使用している例

assert(condition:message:file:line:)関数はそのconditionと messageパラメータのオートクロージャーを取得します。そのconditionパラメーターは、デバッグビルドで評価され、そのmessageパラメータがconditionがfalseの場合にのみ評価されています。

クロージャーは呼び出すまで実行されないため、自動クロージャーを使用すると、評価を遅らせることができます。評価の遅延は、コードが評価されるタイミングを制御できるため、副作用があるか計算コストがかかるコードに役立ちます。

// autoclosure ではない場合
func serve1(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}

// @autoclosure で宣言する場合
func serve2(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
var customers = ["1st", "2nd", "3rd", "4th"]

// autoclosure ではない場合、呼び出しには中括弧が必要
serve1(customer: { customers.remove(at: 0) } )
// Prints "Now serving 1st"

// @autoclosure で宣言する場合、中括弧を省略できる
serve2(customer: customers.remove(at: 0))
// Prints "Now serving 2nd"

注意

自動クロージャを使いすぎると、コードが理解しにくくなる可能性があります。コンテキストと関数名は、評価が延期されていることを明確にする必要があります。

エスケープを許可するオートクロージャーが必要な場合は@autoclosure属性と@escaping属性の両方を使用します。

// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))

print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."
for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"

上記のコードでは、customerProvider引数として渡されたクロージャーを呼び出す代わりに、collectCustomerProviders(_:)関数はクロージャーをcustomerProviders配列に追加します。配列は関数のスコープ外で宣言されています。つまり、関数が戻った後で、配列内のクロージャーを実行できます。その結果、customerProvider引数の値は、関数のスコープをエスケープできるようにする必要があります。

関数

関数の定義と呼び出し

func greet(person: String) -> String {
    let greeting = "Hello, " + person + "!"
    return greeting
}

関数パラメーターと戻り値

// パラメータなしの関数
func sayHelloWorld() -> String {
    return "hello, world"
}

// 複数のパラメーターを持つ関数
func greet(person: String, alreadyGreeted: Bool) -> String {
  if alreadyGreeted {
    return greetAgain(person: person)
  } else {
    return greet(person: person)
  }
}

// 戻り値のない関数
func greet(person: String) {
  print("Hello, \(person)!")
}

注意

厳密に言えば、戻り値が定義されていなくても、関数値を返します。戻り値の型が定義されていない関数は、typeの特別な値 void を返します。これは単に空のタプル()です。

複数の戻り値を持つ関数

関数の戻り値の型としてタプル型を使用して、複数の値を返すことができます。

func minMax(array: [Int]) -> (min: Int, max: Int) {
  var currentMin = array[0]
  var currentMax = array[0]
  for value in array[1..<array.count] {
    if value < currentMin {
      currentMin = value
    } else if value > currentMax {
    currentMax = value
    }
  }
  return (currentMin, currentMax)
}

let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
print("min is \(bounds.min) and max is \(bounds.max)")

オプショナルタプルの戻り値

func minMax(array: [Int]) -> (min: Int, max: Int)? {
    if array.isEmpty { return nil }
    var currentMin = array[0]
    var currentMax = array[0]
    for value in array[1..<array.count] {
        if value < currentMin {
            currentMin = value
        } else if value > currentMax {
            currentMax = value
        }
    }
    return (currentMin, currentMax)
}

注意

タプル全体がオプショナルの(Int, Int)?と、個々のオプショナルの(Int?, Int?)とは意味が異なる。

暗黙的な戻り値を持つ関数

関数の本体全体が単一の式である場合、関数は暗黙的にその式を返します。たとえば、以下の両方の関数は同じ動作をします。

func greeting(for person: String) -> String {
    // return がない、暗黙的な戻り値
    "Hello, " + person + "!"
}
print(greeting(for: "Dave"))

func anotherGreeting(for person: String) -> String {
    return "Hello, " + person + "!"
}
print(anotherGreeting(for: "Dave"))

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

各関数パラメーターには、引数ラベルパラメーター名の両方があります

// 関数の実装で使用されるとき、パラメータ名
func someFunction(firstParameterName: Int, secondParameterName: Int) {
}

// 関数を呼び出すとき、引数ラベル
someFunction(firstParameterName: 1, secondParameterName: 2)

// 引数ラベルを使用すると、読みやすく意図が明確な関数本体を提供しながら、表現力豊かな文のように関数を呼び出すことができます。

// 引数ラベルの省略
// パラメータの引数ラベルが不要_な場合は、そのパラメータの明示的な引数ラベルの代わりにアンダースコア()を記述します。
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
}
someFunction(1, secondParameterName: 2)
// パラメータに引数ラベルがある場合、関数を呼び出すときに引数にラベルを付ける必要があります。

// デフォルトのパラメータ値
// パラメータのタイプの後にパラメータに値を割り当てることにより、関数内の任意のパラメータのデフォルト値を定義できます。デフォルト値が定義されている場合は、関数を呼び出すときにそのパラメーターを省略できます。
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
}
someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) 
someFunction(parameterWithoutDefault: 4) // parameterWithDefault is 12
// デフォルト値を持たないパラメーターを最初に配置します。通常、デフォルト値を持たないパラメータは、デフォルト値を持たないパラメーターより重要です。

// 可変パラメータ
// ...により、可変パラメーターを記述します。
// 関数には、可変引数は1つしか含めることができません。
func arithmeticMean(_ numbers: Double...) -> Double {
    var total: Double = 0
    for number in numbers {
        total += number
    }
    return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8.25, 18.75)
// returns 10.0, which is the arithmetic mean of these three numbers

// 入出力パラメーター
// 関数パラメーターは、デフォルトでは定数です。関数の本体内から関数パラメーターの値を変更しようとすると、コンパイル時エラーが発生します。つまり、誤ってパラメーターの値を変更することはできません。
// パラメータのタイプの直前に inout キーワードを配置することで、入出力パラメータを記述します。入出力パラメーターは、関数によって値が変更され、関数から戻されます。
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

// アンパサンド(&)を変数として引数に入力パラメータとして渡す場合は、関数で変更できることを示します。
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"

// inout 入出力パラメータにデフォルト値を設定することはできません。


// swapTwoInts(_:_:)関数は、単にaとbの値をスワップします。
 "_" により、関数呼び出し時の引数ラベルが省略可能であることを示しています。
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}
// 
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)

関数のタイプ

すべての関数には、パラメーターの型と関数の戻りの型で構成される特定の関数型があります。

例えば:

func addTwoInts(_ a: Int, _ b: Int) -> Int {
    return a + b
}
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
    return a * b
}
// この関数のタイプは (Int, Int) -> Int です。

func printHelloWorld() {
    print("hello, world")
}
// この関数のタイプは、() -> Void です。

関数タイプの使用

関数型は、Swiftの他の型と同じように使用します。たとえば、定数または変数を関数タイプとして定義し、適切な関数をその変数に割り当てることができます。

var mathFunction: (Int, Int) -> Int = addTwoInts

// 割り当てられた関数を次の名前で呼び出すことができます
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 5"

// 一致する型が同じである別の関数を、同じ変数に割り当てることができます。
mathFunction = multiplyTwoInts
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 6"

パラメータ型としての関数型

(Int, Int) -> Int などの関数型を、別の関数のパラメーターとして使用できます。

func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
    print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// Prints "Result: 8"

戻り型としての関数型

関数の型を別の関数の戻り値の型として使用できます。これを行うに->は、戻り関数の戻り矢印の直後に完全な関数タイプを記述します。

func stepForward(_ input: Int) -> Int {
    return input + 1
}
func stepBackward(_ input: Int) -> Int {
    return input - 1
}

func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    return backward ? stepBackward : stepForward
}

var currentValue = 3
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)

print("Counting to zero:")
// Counting to zero:
while currentValue != 0 {
    print("\(currentValue)... ")
    currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// 3...
// 2...
// 1...
// zero!

ネストされた関数

この章でこれまでに出てきたすべての関数は、グローバルスコープで定義されたグローバル関数の例です。ネストされた関数と呼ばれる他の関数の本体内に関数を定義することもできます

ネストされた関数は、デフォルトでは外部から隠されていますが、それを囲んでいる関数から呼び出して使用することができます。囲んでいる関数は、ネストされた関数の1つを返すことにより、ネストされた関数を別のスコープで使用することもできます。

func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    func stepForward(input: Int) -> Int { return input + 1 }
    func stepBackward(input: Int) -> Int { return input - 1 }
    return backward ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero now refers to the nested stepForward() function
while currentValue != 0 {
    print("\(currentValue)... ")
    currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// -4...
// -3...
// -2...
// -1...
// zero!

制御フロー

For-Inループ

for– inループを使用して、配列の項目、数値の範囲、文字列の文字などのシーケンスを反復処理します。

// for-in 配列
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
    print("Hello, \(name)!")
}

// for-in 辞書 
// 取得される順序は保証されません
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
    print("\(animalName)s have \(legCount) legs")
}

// for- in数値範囲
for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}
// 上記の例でindexは、は定数であり、その値はループの各反復の開始時に自動的に設定されます。そのため、index使用前に宣言する必要はありません。let宣言キーワードを必要とせずに、ループ宣言に含めるだけで暗黙的に宣言されます。

// シーケンスの各値が必要ない場合、変数名の代わりにアンダースコアを使用して値を無視できます。
for _ in 1...5 {
    print("Hello")
}

// 半開範囲演算子で for-in
let minutes = 60
for tickMark in 0..<minutes {
    // render the tick mark each minute (60 times)
}

// for-in-stride(from:, to:, by) 
let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
    // render the tick mark every 5 minutes (0, 5, 10, 15 ... 45, 50, 55)
}

// for-in-stride(from:, through:, by) 閉じた範囲
let hours = 12
let hourInterval = 3
for tickMark in stride(from: 3, through: hours, by: hourInterval) {
    // render the tick mark every 3 hours (3, 6, 9, 12)
}

ループ

Swiftには2種類のwhileループがあります。

  • while ループの各パスの開始時にその状態を評価します。
  • repeat– whileループを通過する各パスの終わりにその状態を評価します。

while

while condition {
    statements
}

repeat-while

repeat {
    statements
} while condition

条件文

Swiftには、コードに条件分岐を追加する2つの方法があります。ifステートメントとswitchステートメントです。

if

temperatureInFahrenheit = 90
if temperatureInFahrenheit <= 32 {
    print("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
    print("It's really warm. Don't forget to wear sunscreen.")
} else {
    print("It's not that cold. Wear a t-shirt.")
}

switch

switch some value to consider {
case value 1:
    respond to value 1
case value 2,
     value 3:
    respond to value 2 or 3
default:
    otherwise, do something else
}

暗黙のフォールスルーなし

Swiftのステートメントは、各ケースの最下部をfall through せず、次のケースにすすみません。switchでは、明示的なbreakステートメントを必要とせずに、最初の一致するケースが完了するとすぐに実行を終了します。これにより、switchステートメントがC のステートメントよりも安全で使いやすくなり、誤って複数のケースを実行することが回避されます。break は、Swiftでは必要とされていませんが、使用することができます。

各ケースの本文には、少なくとも1つの実行可能ステートメントが含まれている必要あります。

switch anotherCharacter {
case "a": // 実行文がないため、エラーになる
case "A":
    print("The letter A")
default:
    print("Not the letter A")
}

このような場合は、次のように、ケースをコンマで区切ります。
switch anotherCharacter {
case "a", "A":
    print("The letter A")
default:
    print("Not the letter A")
}

switchで、特定のケースの最後に明示的にフォールスルーするには、fallthroughキーワードを使用します。

インターバルマッチング

ケースの値は、interval に含まれているかどうかを確認できます。

switch approximateCount {
  case 0:
    naturalCount = "no"
  case 1..<5:
    naturalCount = "a few"
  case 5..<12:
    naturalCount = "several"
  default:
     naturalCount = "many"
}

タプル

タプルを使用して、同じステートメントで複数の値をテストできます。タプルの各要素は、異なる値または値の間隔に対してテストできます。または、アンダースコア文字を使用して、可能な値と一致させます。

let somePoint = (1, 1)
switch somePoint {
  case (0, 0):
    print("\(somePoint) is at the origin")
  case (_, 0):
    print("\(somePoint) is on the x-axis")
  case (0, _):
    print("\(somePoint) is on the y-axis")
  case (-2...2, -2...2):
    print("\(somePoint) is inside the box")
  default:
    print("\(somePoint) is outside of the box")
}

value binding

case内で、一時的な定数や変数に一致する値に名前を付けることができます。この動作はvalue vindingと呼ばれます

let anotherPoint = (2, 0)
switch anotherPoint {
  case (let x, 0):
    print("on the x-axis with an x value of \(x)")
  case (0, let y):
    print("on the y-axis with a y value of \(y)")
  case let (x, y):
    print("somewhere else at (\(x), \(y))")
}

where

switchでは、where で条件をチェックする句を追加できる。

let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
  case let (x, y) where x == y:
    print("(\(x), \(y)) is on the line x == y")
  case let (x, y) where x == -y:
    print("(\(x), \(y)) is on the line x == -y")
  case let (x, y):
    print("(\(x), \(y)) is just some arbitrary point")
}

複合case

// case では、複数の値をコンマで記述することができます
// リストが長い場合、パターンは複数行に渡って記述できます
switch someCharacter {
  case "a", "e", "i", "o", "u":
    print("\(someCharacter) is a vowel")
  case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
            "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
    print("\(someCharacter) is a consonant")
  default:
    print("\(someCharacter) is not a vowel or a consonant")
}

// value binding の場合
switch stillAnotherPoint {
  case (let distance, 0), (0, let distance):
    print("On an axis, \(distance) from the origin")
  default:
    print("Not on an axis")
}

Control Transfer 文

Control Transfer 文は、あるコードから別のコードに制御を転送することにより、コードが実行される順序変更します。Swiftには5つの制御転送ステートメントがあります。

  • continue
  • break
  • fallthrough
  • return
  • throw

continue

continue文は、ループに実行中の処理を停止し、ループの次の反復の開始時に再び開始するように指示します。

let puzzleInput = "great minds think alike"
var puzzleOutput = ""
let charactersToRemove: [Character] = ["a", "e", "i", "o", "u", " "]
for character in puzzleInput {
  if charactersToRemove.contains(character) {
    continue
  }
  puzzleOutput.append(character)
}
print(puzzleOutput)
// Prints "grtmndsthnklk"

break

breakステートメントは、制御フローステートメント全体の実行をすぐに終了します。

ループ文内の break文

ループステートメント内で break を使用すると、ループの実行がすぐに終了し、ループの閉じ中かっこの後に制御がコードに移ります。

switchステートメントの中断

switch 内部で使用する場合、すぐにその実行を終了し、} 後のコードに制御を写す。

let numberSymbol: Character = "三" // Chinese symbol for the number 3
var possibleIntegerValue: Int?
switch numberSymbol {
  case "1", "١", "一", "๑":
    possibleIntegerValue = 1
  case "2", "٢", "二", "๒":
    possibleIntegerValue = 2
  case "3", "٣", "三", "๓":
    possibleIntegerValue = 3
  case "4", "٤", "四", "๔":
    possibleIntegerValue = 4
  default:
    break
}
if let integerValue = possibleIntegerValue {
  print("The integer value of \(numberSymbol) is \(integerValue).")
} else {
  print("An integer value could not be found for \(numberSymbol).")
}
// Prints "The integer value of 三 is 3."

fallthrough

Swiftでは、switchステートメントが各ケースの下部から次のケースに落ちることはありません。つまりswitch、最初の一致するケースが完了するとすぐに、ステートメント全体が実行を完了します。対照的に、Cでは、フォールスルーを防ぐためにbreak、すべてのswitchケースの最後に明示的なステートメントを挿入する必要があります。これにより、誤って複数のケースを実行することを回避します。

Cスタイルのフォールスルー動作が必要な場合は、fallthroughキーワードを使用します。

let integerToDescribe = 5
var description = "The number \(integerToDescribe) is"
switch integerToDescribe {
  case 2, 3, 5, 7, 11, 13, 17, 19:
    description += " a prime number, and also"
    fallthrough
  default:
    description += " an integer."
}
print(description)
// Prints "The number 5 is a prime number, and also an integer."

注意

このfallthroughキーワードは、実行に該当するケースのケース条件をチェックしません。これは、c言語と同じ振る舞いです。

ラベル付きステートメント

label name: while condition {
    statements
}

// 例
gameLoop: while square != finalSquare {
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    switch square + diceRoll {
      case finalSquare:
          // gameLoop を終わらせる
         break gameLoop 
      case let newSquare where newSquare > finalSquare:
         // gameLoop を続ける
        continue gameLoop
    default:
        break
   }
}

注意

上記の例で、ラベルを使用しなかった場合、switch の break, continue として扱われる。ラベルを使用すると、終了する必要のある制御ステートメントが明確になります。

早期終了

guardステートメントの後のコードを実行するためには、guard 条件が真でなければならない。if文とは異なり、guard文には常にelse句があります。条件が真でない場合、句内のコードが実行されます。

 func guardTest(status: Int) {
    guard status == 0 else { return }
    print("status == 0 である")
}

guardステートメントがコードブロックを終了するために、returnbreakcontinue、またはthrow、fatalError(_:file:line:)のような、関数やメソッドを呼び出すことができる。
また、早期終了処理は、if文で書くより可読性が良い。

APIの可用性の確認

APIの可用性を確認するためのサポートが組み込まれているため、特定のデプロイメントターゲットで使用できないAPIを誤って使用することはありません。

コンパイラーはSDKの可用性情報を使用して、コードで使用されているすべてのAPIが、プロジェクトで指定されたデプロイメントターゲットで使用できることを確認します。利用できないAPIを使おうとすると、Swiftはコンパイル時にエラーを報告します。

if または guard文で、使用するAPIが実行時に使用できるかどうかに応じて、コードブロックを条件付きで実行できる。

if #available(iOS 10, macOS 10.12, *) {
    // iOS では10以降のみ、macOSでは 10.12以降のみ、及びそれ以外のプラットフォーム
} else {
    // Fall back to earlier iOS and macOS APIs
}