33
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Swift】イニシャライザを全て解説してみる

Posted at

#はじめに
普段何となく使っている人も多いであろうイニシャライザについて、全種類詳しくまとめていきます。

#基本のinit
###イニシャライザの基本のキ

基本の書き方は以下の通りで、イニシャライザの中には初期化するためのコードを書きます。

struct Animal {
    init() {
        //初期化するためのコード
    }
}

ここで大切なことは、このブロックを抜けた段階で初期化済みのインスタンスが返されます。
以下のようにすることで、構造体Animalの全てのプロパティーが設定されて初期化が完了です。

struct Animal {
    let name: String
    let owner: String
    init(name: String, owner: String) {
        self.name = name
        self.owner = owner
    }
}

イニシャライザの重要な任務として、型が持っている全ての保存型プロパティーに値を設定すると言うものがあります。つまり、どれか一つでも値がないまま初期化処理をしようとするとコンパイルエラーになります。逆に言えば、インスタンスを作ることができれば、何かしらのデータは入っていることが保証されています。

###returnの省略
イニシャライザは自分自身を返すのでreturnキーワードが必要ですが、普通は明記する必要はありません。もちろん明記しても問題ありません。失敗可能イニシャライザの説明でたびたびでてきます。

struct Animal {
    let name: String
    let owner: String
    init(name: String, owner: String) {
        self.name = name
        self.owner = owner
        return
    }
}

###制約

イニシャライザの目的は自分自身を初期化することですが、言い換えればイニシャライザ内ではまだselfが初期化されきっていないと言うことです。
以下のように、初期化した後のnameプロパティはアクセス可能ですが、ownerプロパティは初期化されきっていないので、エラーとなります。

struct Animal {
    let name: String
    let owner: String
    init(name: String, owner: String) {
        self.name = name
        print(self.name)
        print(self.owner) // エラー
        self.owner = owner
    }
}

###staticとinit
static varやstatic let を使って定義するプロパティはイニシャライザないで自由に読み書き可能です。
スタティックプロパティはインスタンスの状態に影響しないので、当然と言えば当然です。

struct Animal {
    let name: String
    let owner: String
    static let number = 10
    init(name: String, owner: String) {
        print(Animal.number)
        self.name = name
        self.owner = owner
    }
}

これは、static funcも同様です。理由はstaticメソッド内で使えるプロパティはstaticプロパティのみなので、同様に、インスタンスの状況に影響しません。

struct Animal {
    let name: String
    let owner: String
    static var number = 10
    static func plusOneNumber() -> Int {
        number += 1
        return number
    }
    init(name: String, owner: String) {
        let ownerNumber = Animal.plusOneNumber()
        self.name = name
        self.owner = owner + "\(ownerNumber)番"
    }
}

###初期化のタイミング

イニシャライザの初期化が終わるタイミングは型に定義されている全てのプロパティに値が設定された時です。
つまり、イニシャライザが実行中でも、このタイミングで初期化されているので、その後はインスタンスプロパティ、インスタンスメソッドを利用可能になります。

struct Animal {
    let name: String
    let owner: String
    func printName(_ name: String) {
        print(name)
    }
    init(name: String, owner: String) {
        self.printName("print name") // エラー
        self.name = name
        self.owner = owner
        self.printName("print name") // OK
    }
}

#structとinit

構造体で使われるイニシャライザは、そのデータ領域を初期化する目的で使われます。その種類は以下のようになります。

・イニシャライザ(Initializer)
・全項目イニシャライザ(Memberwise Initializer)
・既定イニシャライザ(Default Initializer)

です。一つ一つ見ていきましょう。

###イニシャライザ
こちらは先ほどの基本のイニシャライザで説明した通りのごく普通のイニシャライザです。

struct Person {
    let name: String
    let age: Int
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

以降はこのイニシャライザに少し機能を追加したようなイニシャライザをみていきましょう。

###全項目イニシャライザ
構造体を定義したときにイニシャライザを定義しないと、全てのインスタンスプロパティを初期化するためのイニシャライザが自動的に実装されます。これを全項目イニシャライザといいます。
以下のようにすると、イニシャライザをわざわざ書かなくても、全項目イニシャライザのおかげで、時間の削減や初期化コードを誤ってしまうという可能性を取り除いてくれます。

struct Person {
    let name: String
    let age: Int
    let birthday: String
    let height: Double
    let weight: Double
    let gender: String
}
省略されたinit
init(name: String,
     age: Int,
     birthday: String,
     height: Double,
     weight: Double,
     gender: String) {
    self.name = name
    self.age = age
    self.birthday = birthday
    self.height = height
    self.weight = weight
    self.gender = gender
}

###既定イニシャライザ
全てのインスタンスプロパティに値を設定すると、引数なしのイニシャライザも追加で実装することができます。
これを既定イニシャライザといいます。
以下のコードでは既定イニシャライザと全項目イニシャライザが実装されます。

struct Person {
    var name: String = "REON"
    var age: Int = 20
}
let person = Person() //既定イニシャライザ
print(person.name) // REON
let person2 = Person(name: "REON2", age: 21) //全項目イニシャライザ
print(person2.name) // REON2

###型拡張とイニシャライザの自動生成
全項目イニシャライザや既定イニシャライザの自動実装は型定義の時点で行われ、型拡張に影響されません。
つまり、型拡張を使ってイニシャライザを追加しても、これらが取り消されることはありません。
以下のように、全て使うことができますね。

struct Person {
    var name: String = "REON"
    var age: Int = 20
}
extension Person {
    init(name: String) {
        self.name = name
    }
}
let person = Person() // 既定イニシャライザ
let person2 = Person(name: "REON") // 型拡張で定義されたイニシャライザ
let person3 = Person(name: "REON", age: 21) // 全項目イニシャライザ
print(person.age) // 20
print(person2.age) // 20
print(person3.age) // 21

#classとinit
クラスのイニシャライザは構造体とは特徴がずいぶん異なり、種類も多くなっています。

・既定イニシャライザ(Default Initializer)
・指定イニシャライザ(Designated Initializer)
・便宜イニシャライザ(Convenience Initializer)
・必須イニシャライザ(Required Initializer)

少し複雑ですが、一つ一つ見ていきましょう。

###既定イニシャライザ
こちらは構造体の時に説明したものと同じです。

class Book {
    let title: String = "REON BOOK"
    let price: Int = 10000
}
let book = Book() // 既定イニシャライザ
print(book.price) // 10000

###指定イニシャライザ
指定イニシャライザとは、インスタンスを生成できる唯一のイニシャライザです。最低一つは実装しなければいけません。
以下のように書くと、構造体では全項目イニシャライザが働いていましたが、クラスでは全項目イニシャライザは無いため、イニシャライザを指定しなければいけません。

class Book { // エラー
    let title: String
    let price: Int
}

スクリーンショット 2021-04-13 4.35.28.png

以下のように、イニシャライザを明記してあげます。

class Book {
    let title: String
    let price: Int
    init(title: String, price: Int) {
        self.title = title
        self.price = price
    }
}

指定イニシャライザのすごいところは、クラスを継承した時に一つでも新たに指定イニシャライザを定義すると、継承元の指定イニシャライザを含む全てのイニシャライザを継承先で隠蔽されるようになっています。
これにより、継承先で思いがけないイニシャライザが使われて、インスタンスが想定通りに初期化できない事態を防いでくれます。
ただし、継承元のイニシャライザを継承先の指定イニシャライザから呼び出すことはできます。継承元のイニシャライザを呼び出すときはsuperキーワードを使います。

class Book {
    let title: String
    var price: Int
    init(title: String, price: Int) {
        self.title = title
        self.price = price
    }
}
class HighPriceBook: Book {
    init(price: Int) {
        super.init(title: "High Price Book", price: price * 1000)
    }
}
let book = HighPriceBook(title: "Book", price: 100) // エラー
let book2 = HighPriceBook(price: 1000) // OK

###便宜イニシャライザ
このイニシャライザは名前の通り、インスタンスを生成しやすくするために用いられる副次的なイニシャライザです。

便宜イニシャライザをいくつ定義してもたどっていくと、必ず指定イニシャライザにたどり着きます。
便宜イニシャライザの書き方はinitの前にconvenienceをつけて、内部ではselfキーワードを使い、自分自身に定義されている他のイニシャライザを呼び出すようにします。
さらに、最終的には指定イニシャライザにたどり着けば良いので、便宜イニシャライザの内部で便宜イニシャライザを呼ぶこともできます。
さらに、便宜イニシャライザはクラスのイニシャライザの中で唯一型拡張(extension)で追加できるイニシャライザです。この特徴は後述します。

class Book {
    let title: String
    var price: Int
    init(title: String, price: Int) {
        self.title = title
        self.price = price
    }
    convenience init(title: String) {
        self.init(title: title, price: 1000)
    }
}
class HighPriceBook: Book {
    convenience init(price: Int) {
        self.init(title: "HighPriceBook", price: 1000000)
    }
}

しかし、ここでおかしなことが起きています。継承先の便宜イニシャライザの内部で、実装にはないイニシャライザがselfを使って呼び出されています。
先ほどの隠蔽の話から混乱してしまうかもしれません。

指定イニシャライザを継承先で実装した時は継承元の指定イニシャライザしか呼び出すことができませんでした。

なぜこのようなことができるのでしょうか。理由は簡単です。

便宜イニシャライザの役割を思い出してみると、あくまで副次的なイニシャライザとして扱われるものでした。既存のイニシャライザが全て有効のままでも支障がないと言うことです。

もう少し踏み込んだ話をすると、繰り返しになりますが、指定イニシャライザを隠蔽しないといけない理由は、継承先のクラスを適切に設計できなくなる問題が潜んでいたからでした。しかし、便宜イニシャライザが残されていると、最終的には継承元のどれかの指定イニシャライザにたどり着くことになり、もはや継承元の指定イニシャライザが隠蔽されていないと同じです。そのため、継承先で指定イニシャライザを実装した場合は継承元の全てのイニシャライザが隠蔽されると言うことは必然的だと言えそうです。

ちなみに、便宜イニシャライザは継承先で定義された場合、内部以外にも外部からでも”全ての”イニシャライザを呼び出すことが可能です。

class Book {
    let title: String
    var price: Int
    init(title: String, price: Int) {
        self.title = title
        self.price = price
    }
    convenience init(title: String) {
        self.init(title: title, price: 1000)
    }
}
class HighPriceBook: Book {
    convenience init(price: Int) {
        self.init(title: "HighPriceBook", price: 1000000)
    }
}
//継承元の指定イニシャライザ
let book = HighPriceBook(title: "Book", price: 100)
//継承元の便宜イニシャライザ
let book2 = HighPriceBook(title: "Book2")
//継承先の便宜イニシャライザ
let book3 = HighPriceBook(price: 2000000)

###必須イニシャライザ
必須イニシャライザとは、指定イニシャライザや便宜イニシャライザに必須性を持たせるイニシャライザのことです。つまり、必須化されたイニシャライザ(必須イニシャライザ)は継承先で隠蔽されないことが約束されます。
もう少し詳しく説明すると、継承先に必須イニシャライザを実装し、継承先でイニシャライザを定義した時に、隠蔽は必須イニシャライザでもされます。しかし、継承先での必須イニシャライザの再実装を強制されます。これにより、隠蔽された必須イニシャライザを再実装しない場合はコンパイルが通りません。

requiredがついているイニシャライザが必須イニシャライザとなります。

class Book {
    let title: String
    var price: Int
    required init(title: String, price: Int) {
        self.title = title
        self.price = price
    }
    required convenience init(title: String) {
        self.init(title: title, price: 1000)
    }
}
class HighPriceBook: Book {
    convenience init(price: Int) {
        self.init(title: "HighPriceBook", price: 1000000)
    }
}

便宜イニシャライザは継承先で実装しても継承元のクラスのイニシャライザを隠蔽しないので、これは問題ありません。(継承先で必須イニシャライザを再定義する必要はありません。)

今度は、継承先で指定イニシャライザを実装してみます。
以下のようなエラーが出るため、Fixを押してみましょう。

スクリーンショット 2021-04-13 5.23.07.png

class Book {
    let title: String
    var price: Int
    required init(title: String, price: Int) {
        self.title = title
        self.price = price
    }
    required convenience init(title: String) {
        self.init(title: title, price: 1000)
    }
}
class HighPriceBook: Book {
    init(price: Int) {
        super.init(title: "HighPriceBook", price: price)
    }
    
    required init(title: String, price: Int) {
        fatalError("init(title:price:) has not been implemented")
    }
}

ちなみに、Swiftでは継承元と同じ名前のイニシャライザが継承先にある場合はoverrideをつけますが、requiredキーワードの時だけは必要ありません。

#enumとinit
実は列挙型もイニシャライザを持つことができます。クラスの時のように複雑ではありませんが、列挙型にしかない特徴もあるので、確認していきましょう。

enum Gender {
    case male
    case female
    init(title: String) {
        switch title {
        case "Mr.":
            self = .male
        case "Ms.", "Miss", "Mrs.":
            self = .female
        default:
            fatalError("想定外の敬称が選択されました。")
        }
    }
}
let gender = Gender(title: "Mr.") // male

Raw型のenumには自動で実装されるイニシャライザがあります。

enum Gender: Int {
    case male = 0
    case female = 1
}
if let gender = Gender(rawValue: 1) {
    print(gender) // female
}

このイニシャライザは以下のような定義になっています。
init?()は失敗可能イニシャライザといいます。詳しくは後述します。

init?(rawValue value: Int) {
    switch value {
    case 0:
        self = .male
    case 2:
        self = .female
    default:
        return nil
    }
}

#タプルとinit
タプルとは複数の型を束ねて扱うことができるため、複合型と言われています。

let status = (100, "AAA")

今までは、以下のようにイニシャライザを呼び出していました。

let int = Int(10) 

タプルでも同じようにできそうです。

let status = (Int, String)(100, "AAA")

問題ありません。もう少し綺麗に書くのであれば、このような感じでしょう。

typealias Status = (Int, String)
let status = Status(100, "AAA")

ここで、イニシャライザを呼んでいるように思えます。しかし、タプルにはイニシャライザは存在しません。厳密に言えば、initで定義されたイニシャライザを持っていないようです。

let status = (Int, String).init(100, "AAA") // エラー

スクリーンショット 2021-04-13 5.48.54.png

構造体などの型であれば、イニシャライザを.initで明記して呼び出すことができます。しかし、タプルの場合はできません。このことから、タプルはinitを持っていないことがわかりました。

タプルは他の型とは違う型です。Swiftでは複合型と名前付き型が存在していて、タプルは複合型の一つです。名前付き型は構造体、クラス、列挙型、プロトコル型です。

さらに、複合型はextensionでイニシャライザだけでなく、メソッドの追加も同じ理由でできません。

複合型はタプル以外にも、関数型と言うものがあります。こちらも少しみてみましょう。

var f: (Int, String) -> String

このようなものを関数型といいます。関数型もタプルと同じで複合型に分類されます。つまり、エクステンションでイニシャライザやメソッドの追加はできません。

#型変換
Swiftではある型からある型に変換する時にもイニシャライザを使うとが多いですね。例えば以下のようにです。

let string =  "10"
let int = Int(string)! // 10

このような変換イニシャライザは用途に応じて2種類に分かれます。
これらは引数ラベル名を使って表現する決まりになっています。
しかし、書く人が自発的に守るものなので、コンパイルエラーになることはないのですが、Swiftらしく書くにはAPIデザインガイドラインに規定されているものをつかうのが一般的です。
それでは、以下の2種類をみていきましょう。

###値を保全する型変換
値を保全する変換イニシャライザは変換元の型の値を変換先の型で"できる限り"再現する型変換を提供してくれます。
例えば、Int型からDouble型に変換するときに小数点数以下が0になる浮動小数点数として再現されます。逆もまた然りです。

値を保全する変換イニシャライザは第一引数のラベル名を省略することで表現します。

struct Owner {
    init(_ animal: Animal)
}

###値の再解釈を伴う型変換
値の再解釈を伴う変換イニシャライザは変換元の値をそのままではなく、何らかの加工をして変換先の型に変換する機能を提供します。
例えば、64ビット整数型のデータを使って64ビットの浮動小数点数型の値を作るみたいな場合に使われます。同じビット列でも整数型の値と浮動小数点数の値とでは全然違う値になるので、値は保全されません。イメージとしては、元の値と似ても似つかないような時にこの分類になるようです。

値の再解釈を伴う変換イニシャライザは第一引数のラベル名で解釈の仕方を説明することで表現します。

let value = Double(bitPattern: 21073102)

#リテラル変換
リテラルとは、10や"kk"のような、プログラム中に数値や文字列などの値を決め打ちするときに使います。
以下のように、1や"k"は決め打ちしています。

let a: Int = 1
let b: String = "k"

この例では、Int型の変数aには1という値が設定され、つまり、それぞれの変数に格納されるインスタンスがリテラルから生成されることは察しがつきます。

###リテラルの仕組み
軽く仕組みを紹介します。

Swiftでは暗黙的なリテラル変換はしないで、変換先の型が責任を持ってリテラルから、自身のインスタンスを生成するしくみになっています。
これらはリテラル表現可能な性質を付加するプロトコルにより実現されています。
一部をみてみましょう。

・ExpressibleByNilLiteral // nilリテラル表現可能
・ExpressibleByIntegerLiteral // 整数リテラル表現可能
・ExpressibleByFloatLiteral // 浮動小数点数リテラル表現可能
・ExpressibleByBooleanLiteral // 真偽値リテラル表現可能
・ExpressibleByStringlLiteral // 文字数リテラル表現可能
・ExpressibleByArrayLiteral // 配列リテラル表現可能
・ExpressibleByDictionaryLiteral // 辞書リテラル表現可能

###リテラルを自作
整数リテラルで表現可能な型を作ってみます。

struct Yen {
    var amount: Int
}
extension Yen: ExpressibleByIntegerLiteral {
    init(integerLiteral value: Int) {
        self.amount = value
    }
}
let price: Yen = 100

当たり前ですが、Yen構造体がExpressibleByIntegerLiteralプロトコルに準拠していなければ、以下のコードはコンパイルエラーになります。

struct Yen {
    var amount: Int
}
let price: Yen = 100

スクリーンショット 2021-04-13 6.32.45.png

しかし一つ疑問が生じます。
このようなExpressibleByIntegerLiteralプロトコルに準拠したYen型を作ってしまえば、以下のようなコードはInt型なのでしょうか、Yen型なのでしょうか。

let value = 200

これは何となく直感的にInt型ではないかと予測はできます。しかし、Swiftは型に厳密な言語です。必ず理由があるはずです。これは既定の型が決められているからです。
整数リテラルの既定の型はIntegerLiteralType = Intと言うふうに決められています。
もう少し踏み込んでみましょう。これを先変えればどうなるでしょうか。つまり以下のコードを定義します。
type(of:)はそのプロパティの型を返してくれます。

struct Yen {
    var amount: Int
}
extension Yen: ExpressibleByIntegerLiteral {
    init(integerLiteral value: Int) {
        self.amount = value
    }
}
let price = 200
print(type(of: price)) // Yen
typealias IntegerLiteralType = Yen
print(type(of: price)) // Yen

面白いのは、typealias IntegerLiteralType = Yenをする前も後も出力結果がYenになることです。このことから、全体で別名の型が定義されているのがわかります。

#protocolとinit
プロトコルにイニシャライザを定義することで、そのプロトコルに準拠させた型にはそのイニシャライザを必ず実装しなければいけません。

protocol SomeProtocol {
    init(title: String, price: Int)
}

ここで、プロトコルで規定したイニシャライザは必ず実行されなければいけません。しかし、クラスのイニシャライザは継承先で隠蔽される可能性があります。このような時はどうしたらよかったのか、思い出してみましょう。
必須イニシャライザにすることでした。つまり、プロトコルで定義したイニシャライザは、必須イニシャライザになります。

protocol SomeProtocol {
    init(title: String, price: Int)
}

class SomeClass: SomeProtocol {
    required init(title: String, price: Int) {
        print(title + "は\(price)円です。")
    }
}

#extensionとinit
この章では、型に直接定義する場合と型拡張で定義する場合との違いを見ていきます。

###イニシャライザの自動実装
全項目イニシャライザや既定イニシャライザは構造体やクラスの定義の時に何も実装しなかった時に自動で実装されました。これらのイニシャライザの実装のされ方が型拡張を使った時に違ってきたりします。

###自動実装される場面
構造体の全項目イニシャライザが自動実装されるのは、型の定義時にイニシャライザが何も直接実行されていない場合です。
以下のコードでは、全項目イニシャライザinit(name:, socre:)と型拡張でのイニシャライザinit(personalNumber:)の二つが使えるようになります。

struct Name { }
struct Record {
    var name: Name
    var score: Int
}
extension Record {
    init(personalNumber in: Int) {
        ...
    }
}

しかし、この型拡張で追加されたイニシャライザを型の定義ないで書いてみると、全項目イニシャライザは自動実装されないため、独自に定義したイニシャライザinit(personalNumber:)だけが有効です。

struct Record {
    var name: Name
    var score: Int
    init(personalNumber in: Int) {
        ...
    }
}

###便宜イニシャライザの拡張
クラスのイニシャライザのなかで型拡張が可能なものは便宜イニシャライザのみになります。

便宜イニシャライザは拡張できる

extension NSColor {
    convenience init(hexTriplet color: HexTripretColor) {
        
    }
}

指定イニシャライザは拡張できない

extension NSColor {
    init(hexTriplet color: HexTripretColor) {
        
    }
}

必須イニシャライザは拡張できない

extension NSColor {
    required init(hexTriplet color: HexTripretColor) {
        
    }
}

###プロトコルとクラス拡張
例えば、以下のようなコードを考えます。

class Person { }
protocol PersonProtocol {
    init(name: String)
}

これを型拡張を使って例えば、PersonProtocolに適用しようとしただけで手詰まりになります。
プロトコルが要求する必須イニシャライザをクラスの型拡張で実装できないためです。

class Person { }
protocol PersonProtocol {
    init(name: String)
}
extension Person: PersonProtocol {
    required init(name: String) {
        print(name)
    }
}

スクリーンショット 2021-04-13 7.43.34.png

しかし、解決策はいくつかあります。

まずは、元も子もありませんが、プロトコルで宣言されていた必須イニシャライザをクラスの定義に直接実装してしまえば良さそうです。

class Person: PersonProtocol {
    required init(name: String) {
        print(name)
    }
}
protocol PersonProtocol {
    init(name: String)
}

もしくは、プロトコルの定義を変更しましょう。
イニシャライザと似た目的で使われがちなstaticメソッドに変えてみるとかでしょうか。

class Person: PersonProtocol {
    static func instantiate(name: String) -> String {
        return name
    }
}
protocol PersonProtocol {
    static func instantiate(name: String) -> String
}
let myName = Person.instantiate(name: "REON") // "REON"

###クラス継承が禁止されている時
クラスで継承が禁止されている時の型拡張でのイニシャライザの追加の挙動は少し違った様子を見せます。

以下のようなfinalキーワードがついたクラスは他のクラスに継承されることがなくなります。

final class SomeClass { }

このようなクラス継承ができないクラスの場合、プロトコルが要求する必須イニシャライザであっっても便宜イニシャライザのように実装できるようになります。
なぜこのようなことができるのかと言うと、プロトコルで規定されたイニシャライザが将来も存在することを保証するためです。これ以降継承されることのないクラスであれば、実装したイニシャライザが将来どこかで隠蔽される心配はありませんね。

#初期化できない場合の対処
例えば、引数に適切なものが与えられていない場合など、インスタンスを生成できない場合があります。そんな場合にどのように対処するべきなのかを見ていきたいと思います。

###型に不正な情報を持たせる
Swiftではあまり使われないみたいですが、型を定義するときに"名前が設定されていなければ無効"のような不正な状態を持たせる方法があります。

struct Person {
    var name: String
    var age: Int
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    var isValid: Bool {
        return !name.isEmpty
    }
}

しかし、このような場合では、どう言う時に初期化失敗を意味するかと言うルールがisValidプロパティの実装にしか現れません。コメントなどで明記する必要などもありそうですね。さらに、初期化に失敗していることを判定し忘れる可能性が生まれるかもしれません。

###プログラムを強制終了させる
初期化できないと分かったタイミングでプログラムを終了すると言う方法もあります。 

struct Person {
    var name: String
    var age: Int
    init(name: String, age: Int) {
        guard !name.isEmpty else {
            fatalError("Empty name in not allowed.")
        }
        self.name = name
        self.age = age
    }
}

この方法では、値が不正なまま実行を進めてしまう心配はなさそうです。初期化が失敗しても状況に応じて処理を復帰できる可能性があるなら、この強制終了ではなく、次のエラーハンドリングを使うか、その次の失敗可能イニシャライザを使うことを考えても良いでしょう。

###エラーハンドリングで通知する
throwsキーワードを利用します。

enum InitError: Error {
    case invalidName
    case unknown
}
struct Person {
    var name: String
    init(name: String) throws {
        guard !name.isEmpty else {
            throw InitError.invalidName
        }
        self.name = name
    }
}

このように、throwsキーワードがついたイニシャライザでインスタンスを生成する時は、do-try-catch文で実行する必要があるため、成功した時と失敗した時で明確に分けることができます。

enum InitError: Error {
    case invalidName
    case unknown
}
struct Person {
    var name: String
    init(name: String) throws {
        guard !name.isEmpty else {
            throw InitError.invalidName
        }
        self.name = name
    }
}
do {
    let person = try Person(name: "REON")
    print(person.name)
} catch InitError.invalidName {
    print("無効な名前です。")
} catch InitError.unknown {
    print("予期せぬエラーが発生しました。")
} catch {
    print(error)
}

また、エラーの種類ごとに分岐が必要でない場合は、以下のようにハンドリングすることも可能です。

if let person = try? Person(name: "REON") {
    print(person.name + "成功")
} else {
    print("失敗")
}

###失敗可能イニシャライザを使う
シンプルに初期化に成功したか失敗したかわかれば十分な時はエラーハンドリングだけではなく、失敗可能イニシャライザも利用できます。
失敗可能イニシャライザは初期化できない段階でreturn nilをします。

struct Person {
    var name: String
    init?(name: String) {
        guard !name.isEmpty else {
            return nil
        }
        self.name = name
    }
}

エラーハンドリングと違うところはコードを書く負担を軽減できることです。得られる結果はエラーハンドリングにtry?を併用した時と同じになりますが、エラーハンドリングでは複数のエラーを考慮するか成功か失敗かの2択にするかで考えてコードを書く必要もあります。もし、その型の初期化で成功したのか失敗したのかにしか関心がないときはinit?()を使う方がSwiftyなコードになると思います。

#おわりに
イニシャライザを制するものはSwiftを制す!(たぶん...)

33
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?