オプショナルの連鎖

オプショナルの連鎖は、現在nilが存在している可能性があるオプショナルプロパティ、メソッド、およびサブスクリプトを照会および呼び出すためのプロセスです。オプショナルに値が含まれている場合、プロパティ、メソッド、またはサブスクリプトの呼び出しは成功します。オプショナルがnilの場合、プロパティ、メソッド、またはサブスクリプトの呼び出しはnilを返します。複数のクエリを連鎖することができ、連鎖内のリンクがnilである場合、連鎖全体が正常に失敗します。

注意

Swiftでのオプショナルの連鎖はObjective-Cでのnilメッセージングに似ていますが、どのタイプでも機能し、成功または失敗を確認できるようになっています。

強制アンラップの代替としてのオプショナルの連鎖

オプショナルがnil以外の場合、プロパティ、メソッド、またはサブスクリプトを呼び出すオプショナル値の後に疑問符(?)を配置して、オプショナル連鎖を指定します。これは、オプショナル値の後に感嘆符(!)を配置して、その値を強制的にアンラップするのとよく似ています。主な違いは、オプショナルがnilの場合はオプショナルの連鎖が正常に失敗するのに対し、オプショナルがnilの場合は強制アンラップが実行時エラーをトリガーすることです。

nil値に対してオプショナルの連鎖を呼び出すことができるという事実を反映するために、オプショナルの連鎖呼び出しの結果は、クエリしているプロパティ、メソッド、またはサブスクリプトが非オプショナル値を返した場合でも、常にオプショナル値になります。このオプショナル戻り値を使用して、オプショナルの連鎖呼び出しが成功した(返されたオプショナルに値が含まれている)かnil、連鎖内の値が原因で成功しなかったか(返されたオプショナル値はnil)を確認できます。

具体的には、オプショナルの連鎖呼び出しの結果は、予期される戻り値と同じ型ですが、オプショナルでラップされます。通常はIntを返すプロパティは、オプショナルの連鎖を通じてアクセスするとInt?を返します。

次のいくつかのコードスニペットは、オプショナル連鎖が強制アンラップとどのように異なるかを示し、成功を確認できるようにします。

まず、2つのクラス Person とResidence を定義します。

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

ResidenceインスタンスにはnumberOfRoomsと呼ばれる単一のIntプロパティがあり、デフォルト値は1です。Personインスタンスには、Residence?型のオプショナルプロパティresidenceがあります。

新しいPersonインスタンスを作成する場合、そのresidenceプロパティはオプショナルであるため、デフォルトでnilに初期化されます。以下のコードでjohnのresidenceプロパティ値はnilになります。

let john = Person()

この人物 residence のnumberOfRoomsのプロパティにアクセスしようとすると、residenceの後に感嘆符を配置してその値を強制的にアンラップすると、アンラップするresidence値がないため、ランタイムエラーが発生します。

let roomCount = john.residence!.numberOfRooms
// this triggers a runtime error

上記のコードjohn.residenceは、非nil値の場合に成功し、roomCountを適切な数の部屋を含むInt値に設定します。ただし、上記のように、このコードは常にresidenceがnilのときにランタイムエラーをトリガーします。

オプショナルの連鎖は、numberOfRoomsの値にアクセスするための代替方法を提供します。オプショナル連鎖を使用するには、感嘆符の代わりに疑問符を使用します。

if let roomCount = john.residence?.numberOfRooms {
    print("John's residence has \(roomCount) room(s).")
} else {
    print("Unable to retrieve the number of rooms.")
}
// Prints "Unable to retrieve the number of rooms."

これは、Swiftにオプショナルresidenceプロパティを「連鎖」し、residencegが存在する場合はnumberOfRoomsの値を取得するように指示します。

numberOfRoomsへのアクセスの試みは失敗する可能性があるため、オプショナル連鎖の試みはInt?型の値または「optional Int」を返します。residenceがnilのとき、上記の例のように、それがnumberOfRoomsにアクセスすることができなかったという事実を反映して、このオプショナルIntはnilになります。オプショナルIntは、オプショナルバインディングを介してアクセスされ、整数をアンラップして、非オプショナルの値をroomCount変数に割り当てます。

これはnumberOfRoomsが非オプショナルIntでも、これは事実です。オオプショナルの連鎖を通じて照会されるという事実は、numberOfRoomsへの呼び出しが常にのInt?代わりにIntを返すことを意味します。

Residenceインスタンスをjohn.residenceに割り当てると、nil値ではなくなります。

john.residence = Residence()

ここで、john.residenceはnilではなく、実際のResidenceインスタンスが含んでています。以前と同じオプショナルの連鎖を使用してnumberOfRoomsをアクセスしようとすると、numberOfRoomsのデフォルト値1の Int? が返されます。

if let roomCount = john.residence?.numberOfRooms {
    print("John's residence has \(roomCount) room(s).")
} else {
    print("Unable to retrieve the number of rooms.")
}
// Prints "John's residence has 1 room(s)."

オプショナル連鎖のためのモデルクラスの定義

複数レベルの深さのプロパティ、メソッド、およびサブスクリプトの呼び出しでオプショナルの連鎖を使用できます。これにより、相互に関連するタイプの複雑なモデル内のサブプロパティにドリルダウンし、それらのサブプロパティのプロパティ、メソッド、およびサブスクリプトにアクセスできるかどうかを確認できます。

以下のコードスニペットは、マルチレベルのオプショナル連鎖の例を含む、後続のいくつかの例で使用する4つのモデルクラスを定義します。これらのクラスは、関連するプロパティ、メソッド、およびサブスクリプトとともに、RoomおよびAddressクラスを追加することにより、上記のPersonおよびResidenceモデルを拡張します。

Personクラスは、前と同じように定義されています。

class Person {
    var residence: Residence?
}

Residenceクラスは、以前よりもより複雑です。今回は、Residenceクラスがroomsと呼ばれる変数プロパティを定義します。これは、[Room]型の空の配列で初期化されます。

class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        get {
            return rooms[i]
        }
        set {
            rooms[i] = newValue
        }
    }
    func printNumberOfRooms() {
        print("The number of rooms is \(numberOfRooms)")
    }
    var address: Address?
}

このバージョンのResidenceRoomインスタンスの配列を格納するため、そのnumberOfRoomsプロパティは、格納プロパティではなく、計算プロパティとして実装されます。計算プロパティnumberOfRoomsは、単にrooms配列からcountプロパティの値を返します。

rooms配列にアクセスするためのショートカットとして、このResidenceバージョンのは、rooms配列内の要求されたインデックスにある部屋へのアクセスを提供する読み書きサブスクリプトが用意されています。

このバージョンのResidenceには、printNumberOfRoomsと呼ばれるメソッドも用意されています。これは、住宅の部屋数を出力するだけです。

最後に、ResidenceというAddress?型のaddressと呼ばれるオプショナルプロパティを定義します。このプロパティのAddressクラスタイプは以下に定義されています。

rooms配列のために使用されるRoomクラスは、nameプロパティを持つ単純なクラスであり、そのプロパティに適切なルーム名を設定するイニシャライザ:

class Room {
    let name: String
    init(name: String) { self.name = name }
}

このモデルの最後のクラスはAddressと呼ばれます。このクラスには、String?型の3つのオプショナルプロパティがあります。最初の2つのプロパティbuildingNamebuildingNumberは、住所の一部として特定の建物を識別するための代替方法です。3番目のプロパティstreetは、その住所の道路に名前を付けるために使用されます。

class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if let buildingNumber = buildingNumber, let street = street {
            return "\(buildingNumber) \(street)"
        } else if buildingName != nil {
            return buildingName
        } else {
            return nil
        }
    }
}

このAddressクラスは、buildingIdentifier()と呼ばれるメソッドも提供します。このメソッドの戻り型はString?です。このメソッドは、アドレスのプロパティをチェックし、値がある場合はbuildingName、または両方に値がある場合はbuildingNumberとstreetを連結したもの、そうでなければ nil を返します。

オプショナル連鎖によるプロパティへのアクセス

強制アンラップの代替としてのオプショナルの連鎖示されているように、オプショナルの連鎖使用して、オプショナル値のプロパティにアクセスし、そのプロパティアクセスが成功したかどうかを確認できます。

上記で定義したクラスを使用して新しいPersonインスタンスを作成し、以前のようにそのnumberOfRoomsプロパティにアクセスしてみます。

let john = Person()
if let roomCount = john.residence?.numberOfRooms {
    print("John's residence has \(roomCount) room(s).")
} else {
    print("Unable to retrieve the number of rooms.")
}
// Prints "Unable to retrieve the number of rooms."

john.residenceは nil であり、このオプショナル連鎖コールは、前と同じように失敗します。

オプショナルの連鎖を通じてプロパティの値を設定することもできます。

let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress

この例では、john.residenceは現在 nil であるため、john.residenceのaddressプロパティを設定しようとしても失敗します。

割り当てはオプショナル連鎖の一部です。つまり、= 演算子の右側のコードは評価されません。前の例では、定数にアクセスしても副作用がないため、someAddressが評価されないことが簡単にわかりません。以下のリストは同じ割り当てを行っていますが、関数を使用してアドレスを作成しています。この関数は、値を返す前に「Function was called」を出力します。これにより、=演算子の右側が評価されたかどうかを確認できます。

func createAddress() -> Address {
    print("Function was called.")

    let someAddress = Address()
    someAddress.buildingNumber = "29"
    someAddress.street = "Acacia Road"

    return someAddress
}
john.residence?.address = createAddress()

何も出力されないため、createAddress() 関数が呼び出されていないことがわかります。

オプショナル連鎖によるメソッドの呼び出し

オプショナルの連鎖を使用して、オプショナル値でメソッドを呼び出し、そのメソッドの呼び出しが成功したかどうかを確認できます。そのメソッドが戻り値を定義していない場合でも、これを行うことができます。

ResidenceクラスのprintNumberOfRooms()メソッドは、numberOfRoomsの現在の値を出力します。メソッドは次のようになります。

func printNumberOfRooms() {
    print("The number of rooms is \(numberOfRooms)")
}

このメソッドは戻り値の型を指定しません。ただし、戻り値のない関数とメソッドには、戻り値のない関数で説明されているように、暗黙的なVoid型の戻り値を持ちます。これは、()の値または空のタプルを返すことを意味します。

If you call this method on an optional value with optional chaining, the method’s return type will be Void?, not Void, because return values are always of an optional type when called through optional chaining. This enables you to use an if statement to check whether it was possible to call the printNumberOfRooms() method, even though the method does not itself define a return value. Compare the return value from the printNumberOfRooms call against nil to see if the method call was successful:

オプショナルの連鎖を使用してオプショナル値でこのメソッドを呼び出す場合、メソッドの戻り値の型はVoidではなくVoid?になります。これは、オプショナルの連鎖を通じて呼び出される場合、戻り値は常にオプショナル型であるためです。これにより、メソッド自体が戻り値を定義していなくても、ifステートメントを使用してprintNumberOfRooms()メソッドを呼び出すことができたかどうかを確認できます。printNumberOfRooms呼び出しからの戻り値をnilと比較して、メソッド呼び出しが成功したかどうかを確認します。

if john.residence?.printNumberOfRooms() != nil {
    print("It was possible to print the number of rooms.")
} else {
    print("It was not possible to print the number of rooms.")
}
// Prints "It was not possible to print the number of rooms."

オプショナルの連鎖を通じてプロパティを設定しようとした場合も同様です。上記のオプショナルの連鎖によるプロパティへのアクセスの例では、residenceプロパティがnilであってもjohn.residenceにaddress値を設定しようとしています。オプショナルの連鎖を介してプロパティを設定しようとすると、Void?型の値が返されます。これをnilで比較して、プロパティが正常に設定されたかどうかを確認できます。

if (john.residence?.address = someAddress) != nil {
    print("It was possible to set the address.")
} else {
    print("It was not possible to set the address.")
}
// Prints "It was not possible to set the address."

オプショナル連鎖によるサブスクリプトのアクセス

オプショナル連鎖を使用して、オプショナル値のサブスクリプトから値を取得および設定し、そのサブスクリプトの呼び出しが成功したかどうかを確認できます。

注意

オプショナル連鎖を介してオプショナル値のサブスクリプトにアクセスする場合、疑問符をサブスクリプトの大括弧の後ではなく前に置きます。オプショナル連鎖の疑問符は常に、オプショナルの式の部分の直後に続きます。

以下の例では、Residenceクラスで定義されたサブスクリプトを使用して、john.residenceプロパティのrooms配列の最初の部屋の名前を取得しようとしています。john.residenceは現在でnilなので、サブスクリプト呼び出しは失敗します。

if let firstRoomName = john.residence?[0].name {
    print("The first room name is \(firstRoomName).")
} else {
    print("Unable to retrieve the first room name.")
}
// Prints "Unable to retrieve the first room name."

このサブスクリプト呼び出しのオプショナル連鎖疑問符は、john.residenceの直後、サブスクリプト括弧の前に配置されます。これは、john.residenceがオプショナル連鎖が試行されるオプショナル値であるためです。

同様に、オプショナル連鎖を伴うサブスクリプトを使用して、新しい値を設定することができます。

john.residence?[0] = Room(name: "Bathroom")

現在residenceは、nilであるため、このサブスクリプト設定の試行も失敗します。

実際のResidenceインスタンスを作成してjohn.residenceに割り当て、そのrooms配列に1つ以上のRoomインスタンスがある場合、Residenceサブスクリプトを使用して、オプショナルの連鎖を通じてrooms配列内の実際のアイテムにアクセスできます。

let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "Living Room"))
johnsHouse.rooms.append(Room(name: "Kitchen"))
john.residence = johnsHouse

if let firstRoomName = john.residence?[0].name {
    print("The first room name is \(firstRoomName).")
} else {
    print("Unable to retrieve the first room name.")
}
// Prints "The first room name is Living Room."

オプショナルタイプのサブスクリプトへのアクセス

サブスクリプトがオプショナルタイプの値(SwiftのDictionaryタイプのキーサブスクリプトなど)を返す場合は、サブスクリプトの閉じ括弧の後に疑問符を付けて、オプショナル戻り値を連鎖します。

var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0] += 1
testScores["Brian"]?[0] = 72
// the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]

上の例では、testScoresという辞書を定義しています。これには、StringキーをInt値の配列にマップする2つのキーと値のペアが含まれています。この例では、オプショナル連鎖を使用して、"Dave"配列の最初の項目を91に設定しています。"Bev"配列の最初の項目を1;増分します。そして、配列の最初の項目を”Brian”のキーに設定しようとします。ので、最初の2つの呼び出しが、testScores辞書が"Dave""Bev"のキーが含まれているため、成功する。testScores辞書には”Brian”のキーが含まれていないため、3番目の呼び出しは失敗します。

複数レベルの連鎖のリンク

オプショナル連鎖の複数のレベルをリンクして、モデル内のより深いプロパティ、メソッド、およびサブスクリプトにドリルダウンできます。ただし、オプショナル連鎖の複数のレベルは、戻り値にオプショナルレベルを追加しません。

別の言い方をすると:

  • 取得しようとしている型がオプショナルではない場合、オプショナル連鎖のためにオプショナルになります。
  • あなたが取得しようとしている型がすでにオプショナルの場合はそれは連鎖のため、オプショナル以上にはなりません。

したがって:

  • オプショナル連鎖を通じてInt値を取得しようとすると、使用されている連鎖のレベルの数に関係なく、常にInt?が返されます。
  • 同様に、オプショナルの連鎖を通じてInt?値を取得しようとすると、使用される連鎖のレベルの数に関係なく、常にInt?が返されます。

以下の例では、johnのresidenceのプロパティのaddressのプロパティのstreetのプロパティをアクセスします。ここでは、どちらもオプショナル型のresidenceおよびaddressのプロパティを連鎖する、オプショナル連鎖の2つのレベルがあります:

if let johnsStreet = john.residence?.address?.street {
    print("John's street name is \(johnsStreet).")
} else {
    print("Unable to retrieve the address.")
}
// Prints "Unable to retrieve the address."

john.residenceの値には現在、有効なResidenceインスタンスが含まれています。ただし、john.residence.address の値は現在nilです。このため、john.residence?.address?.street への呼び出しは失敗します。

上記の例では、streetプロパティの値を取得しようとしていることに注意してください。このプロパティの型はString?です。したがって、プロパティの基になるオプショナルタイプに加えて、2つのレベルのオプショナルの連鎖が適用されていても、john.residence?.address?.streetの戻り値はString?です。

実際のAddressインスタンスをjohn.residence.addressの値として設定し、アドレスのstreetプロパティに実際の値を設定すると、マルチレベルのオプショナルの連鎖を通じてstreetプロパティの値にアクセスできます。

let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence?.address = johnsAddress

if let johnsStreet = john.residence?.address?.street {
    print("John's street name is \(johnsStreet).")
} else {
    print("Unable to retrieve the address.")
}
// Prints "John's street name is Laurel Street."

この例では、john.residenceのaddressプロパティを設定する試みは成功します。これは、john.residenceの値に現在有効なResidenceインスタンスが含まれているためです。

オプショナル戻り値を持つメソッドの連鎖

前の例は、オプショナル連鎖を通じてオプショナルプロパティの値を取得する方法を示しています。オプショナル連鎖を使用して、オプショナル型の値を返すメソッドを呼び出し、必要に応じてそのメソッドの戻り値を連鎖することもできます。

以下の例では、オプショナル連鎖を通じてAddressクラスのbuildingIdentifier()メソッドを呼び出しています。このメソッドは、String?型の値を返します。上記のように、オプショナルの連鎖後のこのメソッド呼び出しの最終的な戻り値の型も次のとおりString?です。

if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
    print("John's building identifier is \(buildingIdentifier).")
}
// Prints "John's building identifier is The Larches."

このメソッドの戻り値に対してさらにオプショナルの連鎖を実行する場合は、メソッドの括弧の後にオプショナルの連鎖の疑問符を配置します。

if let beginsWithThe =
    john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
    if beginsWithThe {
        print("John's building identifier begins with \"The\".")
    } else {
        print("John's building identifier does not begin with \"The\".")
    }
}
// Prints "John's building identifier begins with "The"."

注意

上記の例では、オプショナル連鎖の疑問符を括弧の後に配置しています。これは、連鎖するオプショナル値がbuildingIdentifier()メソッドの戻り値であり、buildingIdentifier()メソッド自体ではないためです。

クラスの終了処理(deinit)

クラスの終了処理 deinitの定義

deinit {
    // パラメータ無し(中括弧無し)
    // クラスのみに使用できる
}

デイニシャライザは、クラスインスタンスの割り当てが解除される直前に呼び出されます。デイニシャライザは、クラスタイプでのみ使用できます。

デイニシャライザの例

        let dog = Dog(name: "Snoopy")
        print(dog.name)
// 結果
// Snoopy
// 当該クラスの deinit が呼ばれる
// deinit Dog   
// 次にスーパークラスの deinit が呼ばれる
// deinit Animal
    
    class Animal {
        var name: String
        var nakigoe: String?
        init(name: String) {
            self.name = name
            self.nakigoe = "designated wanwan"
        }
        deinit {
            print("deinit Animal")
        }
    }

    class Dog: Animal {
        deinit {
            print("deinit Dog")
        }
    }
    

デイニシャライズ

デイニシャライザは、クラスインスタンスの割り当てが解除される直前に呼び出されます。deinitキーワードを使用してイニシャライザinitを作成する方法と同様に、キーワードを使用してデイニシャライザを作成します。デイニシャライザは、クラスタイプでのみ使用できます。

デイニシャライズのしくみ

Swiftは、インスタンスが不要になったときに自動的に割り当てを解除して、リソースを解放します。Swiftは自動参照カウントを通じてインスタンスのメモリ管理を処理する自動参照カウント。通常、インスタンスの割り当てが解除されたときに手動でクリーンアップを実行する必要はありません。ただし、独自のリソースを使用している場合は、追加のクリーンアップを自分で実行する必要がある場合があります。たとえば、ファイルを開いてデータを書き込むカスタムクラスを作成する場合、クラスインスタンスの割り当てを解除する前にファイルを閉じる必要がある場合があります。

クラス定義は、クラスごとに最大1つの初期化子を持つことができます。デイニシャライザはパラメータを取らず、括弧なしで記述されます。

deinit {
// perform the deinitialization
}

デイニシャライザは、インスタンスの割り当て解除が行われる直前に自動的に呼び出されます。自分でデイニシャライザを呼び出すことはできません。スーパークラスのデイニシャライザはサブクラスによって継承され、スーパークラスのデイニシャライザはサブクラスのデイニシャライザの実装の最後に自動的に呼び出されます。サブクラスが独自のデイニシャライザを提供しない場合でも、スーパークラスのデイニシャライザは常に呼び出されます。

インスタンスはデイニシャライザが呼び出されるまで割り当て解除されないため、デイニシャライザは呼び出されたインスタンスのすべてのプロパティにアクセスでき、それらのプロパティに基づいて動作を変更できます(閉じる必要のあるファイルの名前の検索など) )。

実行中のデイニシャライザ

動作中のデイニシャライザの例を次に示します。この例では、単純なゲーム用に2つの新しいタイプ、BankとPlayerを定義しています。このBankクラスは、1万枚を超えるコインを流通させることのできない構成通貨を管理します。ゲームにはBankは1つしか存在できないため、Bankは、現在の状態を格納および管理するためのタイププロパティとメソッドを備えたクラスとして実装されます。

class Bank {
    static var coinsInBank = 10_000
    static func distribute(coins numberOfCoinsRequested: Int) -> Int {
        let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
        coinsInBank -= numberOfCoinsToVend
        return numberOfCoinsToVend
    }
    static func receive(coins: Int) {
        coinsInBank += coins
    }
}

Bankは、そのcoinsInBankプロパティで保持しているコインの現在の数を追跡します。また、コインの配布・回収を扱う 2つのメソッド distribute(coins:)とreceive(coins:)を提供します。

このdistribute(coins:)メソッドは、銀行に十分な量のコインがあることを確認してから、配布します。十分なコインがない場合、Bankは要求された数よりも小さい数を返します(銀行にコインが残っていない場合はゼロを返します)。提供されたコインの実際の数を示す整数値を返します。

このreceive(coins:)メソッドは、受け取った数のコインをBankのコインストアに追加します。

このPlayerクラスは、ゲーム内でプレイヤーを説明しています。各プレーヤーは、いつでも一定数のコインを財布に保管しています。これは、プレーヤーのcoinsInPurseプロパティによって表されます。

class Player {
    var coinsInPurse: Int
    init(coins: Int) {
        coinsInPurse = Bank.distribute(coins: coins)
    }
    func win(coins: Int) {
        coinsInPurse += Bank.distribute(coins: coins)
    }
    deinit {
        Bank.receive(coins: coinsInPurse)
    }
}

Playerインスタンスは、初期化中に銀行から指定された数のコインで初期化されますが、Playerが十分なコインが利用できない場合、インスタンスはその数よりも少ない数を受け取ることがあります。

Playerクラスがwin(coins:)メソッドを定義し、Bankからコインの特定の数を取得し、遊技者の財布にそれらを追加します。Playerクラスには、Playerのインスタンスが解放される直前に呼び出されるデイニシャライザを実装します。ここで、デイニシャライザは単純にすべてのプレーヤーのコインを銀行に返します。

var playerOne: Player? = Player(coins: 100)
print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
// Prints "A new player has joined the game with 100 coins"
print("There are now \(Bank.coinsInBank) coins left in the bank")
// Prints "There are now 9900 coins left in the bank"

新しいPlayerインスタンスが作成され、利用可能な場合は100コインがリクエストされます。このPlayerインスタンスは、playerOneというオプションの変数に格納されます。プレーヤーはいつでもゲームを離れることができるため、ここではオプションの変数が使用されています。オプションを使用すると、現在ゲームにプレーヤーがいるかどうかを追跡できます。

playerOneはオプションであるため、そのcoinsInPurseプロパティにアクセスしてデフォルトのコイン数を出力するとき、およびそのwin(coins:)メソッドが呼び出されるときは常に、感嘆符(!)で修飾されます。

playerOne = nil
print("PlayerOne has left the game")
// Prints "PlayerOne has left the game"
print("The bank now has \(Bank.coinsInBank) coins")
// Prints "The bank now has 10000 coins"
  1. playerOne!.win(coins: 2_000)
  2. print(“PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins”)
  3. // Prints “PlayerOne won 2000 coins & now has 2100 coins”
  4. print(“The bank now only has \(Bank.coinsInBank) coins left”)
  5. // Prints “The bank now only has 7900 coins left”

ここでは、プレイヤーは2,000コインを獲得しています。プレイヤーの財布には2,100コインが含まれ、銀行には7,900コインしか残っていません。

  1. playerOne = nil
  2. print(“PlayerOne has left the game”)
  3. // Prints “PlayerOne has left the game”
  4. print(“The bank now has \(Bank.coinsInBank) coins”)
  5. // Prints “The bank now has 10000 coins”

プレイヤーはゲームを離れました。これは、オプションのplayerOne変数を「Playerインスタンスなし」を意味するnilに設定することで示されます。これが発生した時点で、playerOne変数へのPlayerインスタンスへの参照は壊れています。他のプロパティや変数はまだPlayerインスタンスを参照していないため、メモリを解放するために割り当てが解除されます。これが発生する直前に、そのデイニシャライザが自動的に呼び出され、そのコインが銀行に返されます。

初期化2

Swift programming language の Initialization の章が分かりにくいので、初期化2に簡単にまとめます。

イニシャライザー

// 最も簡単なイニシャライザー
    class Animal {
        var name: String = ""
        var nakigoe: String = ""
        init() {
        }
    }

// init でプロパティ値を初期化
    class Animal {
        var name: String
        var nakigoe: String
        init() {
            name = ""
            nakigoe = ""
        }
    }

// initの書式
//  init(ラベル名 引数名: 型) { 式 }
  let animal = Animal(animalName: "animal")

    class Animal {
        var name: String
        var nakigoe: String?
        init(animalName name: String) {
            self.name = name
        }
    }

// 引数ラベルのない初期化パラメーター
  let animal = Animal("animal")

    class Animal {
        var name: String
        var nakigoe: String?
        init(_ name: String) {
            self.name = name
        }
    }

// すべてのプロパティのデフォルト値が提供されている場合、initは省略できる
// nakigoeは、オプショナルString のため、デフォルト値 nil が与えられる
    class Animal {
        var name: String = ""
        var nakigoe: String?
    }
    
// メンバーの初期化例
  let animal = Animal(name: "animal", nakigoe: nil)

    class Animal {
        var name: String = ""
        var nakigoe: String? = nil
        init(name: String, nakigoe: String?) {
            self.name = name
            self.nakigoe = nakigoe
        }
    }

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

// 指定イニシャライザ
// すべてのクラスには、少なくとも1つの指定イニシャライザが必要
init(parameters) {
    statements
}

// コンビニエンスイニシャライザは二次的なもの
// クラスのイニシャライザをサポートする
convenience init(parameters) {
    statements
}
  • ルール1 指定イニシャライザは、直接のスーパークラスから指定イニシャライザを呼び出す必要があります。
  • ルール2 コンビニエンスイニシャライザは、同じクラスから別のイニシャライザを呼び出す必要があります。
  • ルール3 コンビニエンスイニシャライザは、最終的に指定イニシャライザを呼び出す必要があります。
../_images/initializerDelegation01_2x.png
    class Animal {
        var name: String
        var nakigoe: String?
        // 指定イニシャライザー
        init(_ name: String) {
            self.name = name
        }
        // コンビニエンスイニシャライザー
        convenience init(name: String, nakigoe: String)
        {
            self.init(name)
            self.nakigoe = nakigoe
        }
        func naku(){
            print(nakigoe ?? "")
        }
    }
// スーパークラス内のコンビニエンスイニシャライザー
        convenience init(name: String, nakigoe: String)

// サブクラスからスーパークラスのコンビニエンスイニシャライザーを
// オーバーライドしようとすると、次のエラーが発生する
        override convenience init(name: String, nakigoe: String)
// Initializer does not override a designated initializer from its superclass

2フェーズ初期化

フェーズ1では、すべての格納プロパティに初期値が割り当てられます。
フェーズ2では、各クラスに格納されたプロパティをカスタマイズできる。

    class Animal {
        var name: String
        var nakigoe: String?
        init(_ name: String) {
            self.name = name
        }
        convenience init(name: String, nakigoe: String)
        {
            self.init(name)
            self.nakigoe = nakigoe
        }
        func naku(){
            print(nakigoe ?? "")
        }
    }

    class Dog: Animal {
        override init(_ name: String) {
            // フェーズ1  スーパークラスのイニシャライザーを呼ぶ必要がある
            super.init(name)
            // フェーズ2 カスタマイズできる
            self.nakigoe = "wanwan" 
        }
    }

// super.init を呼ばないと、次のエラーになる
// 'self' used in property access 'nakigoe' before 'super.init' call
// 'super.init' isn't called on all paths before returning from initializer

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

// ルール1
// サブクラスが指定イニシャライザを定義していない場合、
// 自動的に Animal のinit が継承される
   class Animal {
        var name: String
        var nakigoe: String?
        init(_ name: String) {
            self.name = name
            self.nakigoe = "designated wanwan"
        }
        convenience init(name: String, nakigoe: String)
        {
            self.init(name)
            self.nakigoe = "convinience wanwa"
        }
        func naku(){
            print(nakigoe ?? "")
        }
    }

    class Dog: Animal {
        // サブクラスが指定イニシャライザを定義していない場合、
        // 自動的に Animal のinit が継承される
    }

//         let dog = Dog("Snoopy")
//        dog.naku()
// 結果: designated wanwan
// ルール2
// サブクラスがすべてのスーパークラスの指定イニシャライザの実装を提供する場合
// スーパークラスのすべてのコンビニエンスイニシャライザを自動的に継承する
    class Animal {
        var name: String
        var nakigoe: String?
        init(_ name: String) {
            self.name = name
            self.nakigoe = "designated wanwan"
        }
        convenience init(name: String, nakigoe: String)
        {
            self.init(name)
            self.nakigoe = nakigoe
        }
        func naku(){
            print(nakigoe ?? "")
        }
    }

    class Dog: Animal {
        override init(_ name: String) {
            super.init(name)
            self.name = name
            self.nakigoe = "designated wanwan"
        }
    }

//        let dog = Dog(name: "Snoopy", nakigoe: "superclass's convinience init wanwan")
//       dog.naku()
// 結果: superclass's convinience init wanwan

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

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

        if let animal = Animal("")
        {
            // 初期化に失敗して、animal はnil になるため、次の行は実行されない
            animal.naku()
        }
    
    class Animal {
        var name: String
        var nakigoe: String?
        // 失敗可能なイニシャライザ
        init?(_ name: String?) {
            if name == "" {
                // return nil で初期化に失敗したことをトリガーする
                return nil
            }
            self.name = name!
            self.nakigoe = "designated wanwan"
        }
        func naku(){
            print(nakigoe ?? "")
        }
    }

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

サブクラスでスーパークラスの失敗可能なイニシャライザをオーバーライドできます。また、スーパークラスの初期化に失敗しても、初期化に失敗しないサブクラスでオーバーライドできます。

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

暗黙的にアンラップされたオプションのインスタンスを作成する、失敗可能なイニシャライザ init! を定義できます。

        if let animal = Animal("")
        {
            animal.naku()
        }
    
    class Animal {
        var name: String
        var nakigoe: String?
        init!(_ name: String?) {
            if name == "" {
                self.name = "default name"
            }
            self.name = name!
            self.nakigoe = "designated wanwan"
        }
        func naku(){
            print(nakigoe ?? "")
        }
    }

必須イニシャライザ

    class Animal {
        var name: String
        var nakigoe: String?
        // すべてのサブクラスがそのイニシャライザを実装する必要があることを示す
        required init(name: String) {
            self.name = name
            self.nakigoe = "wanwan"
        }
    }

    class Dog: Animal {
        // override は不要
        required init(name: String)
        {
            super.init(name: name)
            self.name = "dog: " + name
        }

初期化

初期化は、クラス、構造体、または 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の他の定数または変数と同様に定義されます。標準ライブラリは、ポインタと直接対話する必要がある場合に使用できるポインタとバッファタイプを提供します。「手動メモリ管理」を参照してください。