Tag: Swift/初期化

目次

関連ページ

参考情報

基本

  • クラスや構造体の使用するための準備。
  • プロパティの設定やその他初期化処理を行う。

プロパティへの初期値の設定

  • クラスや構造体は保存プロパティに適切な値を設定して初期化する必要がある。
  • initializerやプロパティ定義時のデフォルト値を使用する

initializers

  • initializerは特定の型のインスタンスを生成する際呼び出される。最も簡単な形式は以下の通り。
    init() {
        // perform some initialization here
    }
    
  • 以下の構造体はtemperatureプロパティを初期化する。
    truct 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"
    

デフォルトプロパティ値

  • 以下のように初期化することもできる。
    struct Fahrenheit {
        var temperature = 32.0
    }
    
    

カスタム初期化

  • 入力パラメータとオプショナルプロパティの型、定数プロパティを割り当てることによって初期化することもできる。

初期化パラメータ

  • 初期化パラメータをinitializerに渡すことができる。摂氏または華氏で初期化できる構造体。
    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
    

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

  • 関数と同じようにinitializerの本体で使用できるパラメータ名と、呼び出し時に使用できる引数ラベルの両方を指定できる。
  • しかしinitializerは独自の名前をもたないので、Swiftは独自の引数ラベルを割り当てる。
    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
        }
    }
    let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
    let halfGray = Color(white: 0.5)
    

引数ラベルなしのイニシャライザ

  • 引数ラベルが不要な場合"_"を指定する。
    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
    
    

オプショナルプロパティタイプ

  • カスタムタイプが保存プロパティを持つ場合、そしてそのプロパティが"値を持たない"ことを表現する必要がある場合、オプショナルタイプはnilで指定される。
  • 以下の例ではresponseがnilで初期化される。
    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."
    

初期化中の定数プロパティの割り当て

  • 初期化中に定数プロパティに値を割り当てることができる。
  • 一度値がわりあてられるとその後変更することはできない。
  • 以下の例では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.)"
    
    

デフォルトinitializers

  • Swiftは構造体やクラスにinitializerが一つも存在しない場合、全てのプロパティをデフォルト値で初期化するデフォルトinitializerを提供する。
  • 以下の例ではデフォルト値で初期化されたインスタンスが生成される。
    class ShoppingListItem {
        var name: String?
        var quantity = 1
        var purchased = false
    }
    var item = ShoppingListItem()
    
  • ShoppingListItemの全てのプロパティはデフォルト値を持ち、スーパークラスを持たないベースクラスなのでデフォルトinitializerが利用できる。

構造体のためのMemberwise initializer

  • 構造体の場合カスタムinitializerが存在しない場合、memberwise initializerが作成される。
  • memberwise initializerは、プロパティを初期化できるイニシャライザー。
    struct Size {
        var width = 0.0, height = 0.0
    }
    let twoByTwo = Size(width: 2.0, height: 2.0)
    
    

値型のためのinitializer delegation

  • initializerはほかのinitializerを呼び出すことができる。これをinitializer delegationと呼ぶ。
  • initializer delegationの仕組みは値型とクラス型によって異なる。
  • 値型(構造体と列挙型)は継承をサポートしておらず、initializer delegationの仕組みはシンプル。
  • しかしクラス型は他のクラスを継承できるので、保存型プロパティが適切に初期化されるよう考慮する必要がある。
  • 値型の場合self.initを利用して他のinitializerを呼び出すことができる(self.initはinitializerからだけ利用できる)。
  • 値型でカスタムinitializerを作成した場合、デフォルトinitializerや(memberwise initializer)は利用できない。
  • これによって作成されたinitializerを回避する、デフォルトinitializerが間違えて呼び出されてしまうのを回避できる。
  • Rect構造体は、Size、Pointをプロパティとして持つ。
    struct Size {
        var width = 0.0, height = 0.0
    }
    struct Point {
        var x = 0.0, y = 0.0
    }
    
    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)
        }
    }
    
  • ユーザーは3つの方法でRectを初期化できる。
    • ゼロで初期化
      let basicRect = Rect()
      // basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)
      
    • 原点で初期化
      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)
      
      
    • 中心で初期化
      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)
      

クラス継承と初期化

  • 全てのクラスの保存プロパティは初期化中に値を割り当てられないといけない(スーパークラスの保存プロパティも含む)。
  • Swiftはこれを確実にするためクラス型に2種類のinitializerを定義する。
  • それはdesignated initialzerとconvenience initializerとして知られる。

Designated initializerとConvenience Initializer

  • Designated initializer(指定イニシャライザ)は、クラスの中でもっとも重要なinitializerとなる。
  • クラスの全てのプロパティを初期化し、スーパークラスの適切なinitializerを呼び出す。
  • クラスは通常一つあるい少数の指定イニシャライザを持ち、
  • Designated initializerは初期化処理が注ぎ込まれる場所で、ここを通してスーパークラスの初期化プロセスが行われることになる。
  • 全てのクラスは最低一つのdesignated initializerを持つ必要がある。
  • Automatic Initializer inheritanceによってスーパークラスからdesignated initializerを継承することによって、要求を満たすこともできる。
  • Convenience Initializerは、2番目のinitializerである。同じクラスに定義されたdesignated initializerを呼び出して定義できる。

Designated initializerとConvenience Initializerのシンタックス

  • Designated initializerは値型のためのinitializerと同じように定義できる。
    init(parameters) {
        statements
    }
    
  • Convenience Initializerは以下のようなmodifierを指定する。
    convenience init(parameters) {
        statements
    }
    
    

クラス型のためのinitializer delegation

  • designated、convenience initializerの関連をシンプルかするため、Swiftは以下のルールを適用する。
    • Rule 1: designated initializerは、直近のスーパークラスのdesignated initializerを呼び出す必要がある。
    • Rule 2: convenience initializerは同じクラスのinitializerを呼び出す必要がある。
    • Rule 3: convenience initializerは最終的にはdesignated initializerを呼び出さなければならない。
  • これを覚えるには
    • designated initializerは常に上位に委譲する。
    • convenience initializerは常に同じクラス内で委譲する。

※Appleサイトに分かりやすい図がある。

ツーフェーズinitialization

  • Swiftのクラスの初期化は2フェーズ。
  • 最初のフェーズではそのクラスの保存プロパティの初期化で、初期化が終わると2番目のフェーズが始まり、保存プロパティをカスタマイズする段階が味丸。
  • 2フェーズの初期化プロセスは、初期化を安全に行う。プロパティが初期化されるまえにアクセスされるこを防ぐ。
  • Swiftのコンパイラは初期化がエラーなしに完成したか確認するため以下のチェックを行う
    • Safety check 1: クラスのプロパティがスーパークラスのプロパティを初期化する前に全て初期化されたかどうか。
    • Safety check 2: designated initializeは継承プロパティにアクセスする前に、スーパークラスのinitializerを呼び出す必要がある。
    • Safety check 3: convenience initializerはプロパティにアクセスする前に、他のinitializerを呼び出す必要がある。
    • Safety check 4: initializerはインスタンスメソッドを呼び出すことができない。プロパティを呼び出すことができない。
  • インスタンスは最初のフェーズが終了するまで完全ではない。最初のフェーズが終了した後プロパティアクセスやメソッドの呼び出しが可能となる。
  • これに基づき初期化は以下の手順で行われる。
  • Phase 1:
    • designated or convenience initializerが呼び出される。
    • クラスの新しいインスタンスのメモリが割り当てられる。メモリはまだ初期化されていない。
    • designated initializerはクラスで導入された保存プロパティが全て値を持っていることを確認する。
    • designated initializerはスーパークラスのinitializerを呼び出す。
    • 継承チェーンを辿りトップまで繰り返す。
    • トップに到達するとインスタンスのメモリの初期化が完了したとみなされphase 1が完了する。
  • Phase 2:
    • 継承チェーンを戻り、それぞれのdesignated initializerはインスタンスをカスタマイズするチャンスを得る。initializerはselfにアクセス可能でプロパティを変更したりメソッドを呼び出せる。
    • 最終的にconvenience initializerにもそのチャンスが巡ってくる。

initializer継承とオーバーライド

  • Objective-Cと異なり、Swiftのサブクラスはスーパークラスのinitializerをデフォルトで継承しない。
  • Swiftの方法はサブクラスの複雑なinitializerが呼び出されず初期化が不完全なインスタンスを作成されるのを防ぐ。
  • もしスーパクラスと同じinitializerを定義したい場合、サブクラスで明示的に定義しないといけない。
  • もしスーパークラスと全く同じdesignated initializerを定義する場合、オーバーライドによって効率的に定義できる。
  • overrideモディファイアが存在すると、Swiftはスーパークラスが同じdesignated initialzierを持っているかチェックする。
  • 逆にサブクラスのinitializerがスーパークラスのconvenience initializerにマッチするとき、superclassのconvenience initializerはサブクラスから直接よびだすことができない(ルールにより)。
  • よって厳密にいえば、overrideはスーパークラスのconvenience initializerをオーバーライドしているとはいえない。
  • 以下の例を考える。
    class Vehicle {
        var numberOfWheels = 0
        var description: String {
            return "\(numberOfWheels) wheel(s)"
        }
    }
    
  • Vehicleクラスはデフォルト値を提供し、カスタムinitializerを提供していないので、デフォルトinitializerが自動的に作成される。
  • デフォルトinitializerは自動的にdesignated initializerとみなされる。
    class Bicycle: Vehicle {
        override init() {
            super.init()
            numberOfWheels = 2
        }
    }
    
  • Bicycleはカスタムdesignated initializerを定義する。これはスーパークラスのデフォルトinitializerと一致するのでoverrideが必要となる。
  • init()はsuper.init()から始まる。これはnumberOfWheelsが初期化されていることを確実にする。

自動initializer継承

  • サブクラスは自動でinitializerを継承しない。しかし特定の場合継承される場合もある。
  • これによって多くの場合、サブクラスでoverrideする必要がなくなる。
  • サブクラスでデフォルト値を定義していたと想定すると、以下のルールが適用される。
    • Rule 1: サブクラスがdesignated initializerを定義していない場合、自動的にスーパークラスのdesignated initializerが継承される。
    • Rule 2: サブクラスがスーパークラスの全てのdesignated initializerの定義を提供していた場合、自動的に全てのスーパークラスのconvenience initializerが継承される。

Designated and Convenience Initializerの動作

  • 以下の例は、designated initializer、convenience initializer、automatic initializer inheritanceの実例。
  • Food、RecipeIngredient、ShoppingListItemがからむ。
    class Food {
        var name: String
        init(name: String) {
            self.name = name
        }
        convenience init() {
            self.init(name: "[Unnamed]")
        }
    }
    
  • Foodクラスはdesignated initializerとconvenience initializerを持つ。
  • 2番目のクラスはRecipeIngredientでFoodを継承する。
    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は一つのdesignated initialzierと一つのconvenience initializerを持つ。
  • convenience initializerはスーパークラスのinitializerと同じシグニチャを持つので、overrideが必要となる。
  • RecipeIngredientは全てのスーパークラスのdesignated initializerを継承していることになるので、自動的にスーパークラスのconvenience initializerであるinitが自動継承される。
  • 最後のクラスはRecipeIngredientのサブクラスであるShoppingListItem。
    class ShoppingListItem: RecipeIngredient {
        var purchased = false
        var description: String {
            var output = "\(quantity) x \(name)"
            output += purchased ? " ✔" : " ✘"
            return output
        }
    
  • プロパティにはデフォルト値が提供されていて独自のinitializerは定義されていないので、3つのinitializerが自動的に継承される。
    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)
    }
    
    

Failable Initializers

  • 失敗する可能性のあるinitializerを定義できる。init?を使用する。
  • failable initializerはreturn nilによって失敗を表す。
    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"
    
  • 例えば以下のように定義できる。
    struct Animal {
        let species: String
        init?(species: String) {
            if species.isEmpty { return nil }
            self.species = species
        }
    }
    
    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"
    
    

Failabe Initializer for Enumerations

  • 列挙型と組み合わせる。
    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
            }
        }
    }
    

Failable Initializer for Enumerations with Raw Values

  • 列挙型はrawValueを受け取るinitializerを自動で作成する。
    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."
    
    

初期化失敗の伝達

  • 初期化の失敗を伝達することを考える。
    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)
        }
    }
    
    
  • スーパークラス、サブクラスどちらの条件が満たされなくてもnilが蛙。

Failable Initializerのオーバーライド

  • failable initializerはオーバーライドできる。またはスーパークラスのfailable initializerをサブクラスの通常のinitializerでオーバーライドすることもできる。
    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
        }
    }
    
    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?をオーバーライドし、失敗しないようになっている。
  • サブクラスでは強制アンラッピングをinitializerの中で呼び出すこともできる。例えばUntitledDocumentは、常に名前を設定するので、initの末尾に"!"がついてる。
    class UntitledDocument: Document {
        override init() {
            super.init(name: "[Untitled]")!
        }
    }
    

init! failable initializer

  • 失敗する可能性のあるinitializerとして普通はinit?を使うが、init!を使うこともできる。init?とinit!はオーバーライドできる。

Required Initializers

  • requiredモディファイアは、全てのサブクラスが実装しなければならないinitializerを意味する。
        required init() {
            // initializer implementation goes here
        }
    }
    
  • サブクラスでもrequiredをつけて実装する。
    class SomeSubclass: SomeClass {
        required init() {
            // subclass implementation of the required initializer goes here
        }
    }
    

Closure or Functionによるデフォルト値の設定

  • プロパティは以下のように実装できる。
    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
        }()
    }
    
  • 以下のような複雑な初期化も可能。
    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]
        }
    }
    
    

落ち穂拾い

サブクラスからのsuper.init()の呼び出し

  • 【備忘録】initの動作検証 - Qiita
  • サブクラスのイニシャライザからsuper.init()を呼び出せば、それに従って処理がおこなわれる。ただしsuper.init()が自動で呼び出せるパターンもある。
    //super.init()の有無による実行順序の違い
    class Parent {
        let name = "Parent name"
        init() {
            print("\(name)")
        }
    }
    
    class Child : Parent{
        let childName = "Child name"
        override init() {
    //        super.init()
            print("\(childName)")
        }
    }
    let child = Child()
    //以下がprintされる。
    //Parent name
    //Child name
    //
    //super.init()をコメントアウトすると逆転する
    //Child name
    //Parent name
    

super.init()の自動呼び出し

  • [swift-users] super.init() called automatically?によると、スーパークラスに一つの引数なしのdesignated initializerが存在する場合、super.init()を明示的に呼び出す必要がない。NSObjectのために作られた?
    //super.init()の自動呼び出し。
    //引数なしの一つのdesignated initializerが存在する場合、サブクラスから自動で呼び出される。
    class Foo {
        init() {
            print("foo init")
        }
    }
    
    class Bar: Foo {
        init(x: Int) {
            print("bar init")
        }
    }
    
    let bar = Bar(x: 10)
    //bar init
    //foo init
    
  • 対して以下の場合は自動で呼び出されない。
    //super.init()が自動で呼び出されない場合。
    class Parent2 {
        let name: String
        init(name: String) {
            self.name = name
            print("Parent2=\(name)")
        }
        
        init() {
            self.name = "Unknown"
        }
    }
    
    class Child2: Parent2 {
        let childName: String
        override init(name: String) {
            self.childName = name
    //        super.init(name: name + "'s parent")
            super.init()
        }
    }
    let child2 = Child2(name: "abc")
    
    

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-09-01 (金) 14:59:00