初期化に失敗する可能性があるクラスや構造体、列挙型を定義すると効果的な場合があります。この失敗は、初期化が成功するのを妨げる無効な初期化パラメータ値や、必要な外部リソースの欠如、その他の条件によって引き起こされます。

失敗する可能性がある初期化条件を扱うために、クラスや構造体、列挙型の定義の一部として、failable イニシャライザを 1 つ以上定義します。init キーワードの後にクエスチョンマークを置いて failable イニシャライザ (init?) を記述します。

NOTE
失敗する可能性がある failable イニシャライザと、失敗することが無い nonfailable イニシャライザを、同じパラメータの型と名前で定義することはできません。

failable イニシャライザは初期化する型のオプショナル値を生成します。failable イニシャライザ内に return nil を記述して、初期化が失敗する可能性がある時点を示します。

NOTE
厳密に言うと、イニシャライザは値を返しません。その役割は、初期化が終了する時までに、完全かつ正確に self が初期化されるようにすることです。初期化の失敗を起こすために return nil を記述しますが、初期化の成功を示すには return キーワードを使用しません。

次の例では、String の定数プロパティ species を持つ構造体 Animal を定義しています。また、構造体 Animal は species パラメータを取る failable イニシャライザを定義しています。このイニシャライザは、イニシャライザに渡された species 値が空文字列かどうかをチェックします。空文字列の場合には、初期化失敗になります。そうでなければ、species プロパティの値が設定されて、初期化が成功します。

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}

この failable イニシャライザを使用して新しい Animal インスタンスを初期化し、初期化が成功しているかをチェックすることができます。

let someCreature = Animal(species: "Giraffe")
// someCreature は Animal? 型で、Animal 型ではない

if let giraffe = someCreature {
    print("An animal was initialized with a species of \(giraffe.species)")
}
// "An animal was initialized with a species of Giraffe" と出力

failable イニシャライザの species パラメータに空文字列の値を渡した場合、イニシャライザは初期化失敗を起こします。

let anonymousCreature = Animal(species: "")
// anonymousCreature は Animal? 型で、Animal 型ではない

if anonymousCreature == nil {
    print("The anonymous creature could not be initialized")
}
// "The anonymous creature could not be initialized" と出力
NOTE
"Giraffe" でなく "" のような)空文字列の値をチェックすることは、オプショナル String の値が無いことを示す nil をチェックすることと同じではありません。上の例で、空文字列 ("") は有効で、オプショナルでない String です。しかし、動物の species プロパティの値が空文字列であることは適切ではありません。この制約をモデル化するために、空文字が見つかった場合に failable イニシャライザは初期化失敗を起こします。

列挙型の failable イニシャライザ

1 つ以上のパラメータに基いて適切な列挙型のケースを選択するために、failable イニシャライザを使用することができます。そして、渡されたパラメータが適切な列挙型のケースに一致しない場合、初期化は失敗します。

次の例では、3 つの状態 (KelvinCelsiusFahrenheit) を持つ列挙型 TemperatureUnit を定義しています。温度記号を表現する Character 値に適切な列挙型ケースを見つけるために、 failable イニシャライザが使用されています。

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 つの状態に合う適切な列挙型のケースを選択するために、およびパラメータが 3 つの状態に一致しない場合に初期化を失敗させるために、この failable イニシャライザを利用することができます。

let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
    print("This is a defined temperature unit, so initialization succeeded.")
}
// "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.")
}
// "This is not a defined temperature unit, so initialization failed." と出力

raw 値による列挙型のイニシャライザ

raw 値による列挙型には、適切な raw-value 型の rawValue パラメータを受け取り、見つかった場合には一致する列挙型ケースを選択し、一致する値が無い場合には初期化失敗を起こす failable イニシャライザ init?(rawValue:) が自動的にできます。

Character 型の raw 値を使用して init?(rawValue:) イニシャライザを利用するように、上の TemperatureUnit の例を上書きすることができます。

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.")
}
// "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.")
}
// "This is not a defined temperature unit, so initialization failed." と出力

初期化失敗の伝播

クラスや構造体、列挙型の failable イニシャライザは、同じクラスや構造体、列挙型にある別の failable イニシャライザにデリゲートすることができます。同様に、サブクラスの failable イニシャライザは、スーパークラスの failable イニシャライザにデリゲートすることができます。

どちらのケースでも、初期化に失敗する別のイニシャライザにデリゲートする場合、初期化処理全体が即座に失敗し、初期化コードはそれ以上実行されません。

NOTE
failable イニシャライザは、nonfailable イニシャライザにデリゲートすることも可能です。失敗することが無い既存の初期化処理に、失敗する可能性がある状態を追加する必要がある場合には、このアプローチを利用します。

次の例では、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 の failable イニシャライザは、始めに 1 以上の quantity 値を受け取っているかを確認します。quantity が無効な場合、初期化処理全体が即座に失敗し、初期化コードはそれ以上実行されません。Product の failable イニシャライザは、同じように name 値をチェックし、name が空文字列の場合には、イニシャライザの処理は即座に失敗します。

空でない名前で、かつ 1 以上の数量で CartItem インスタンスを生成する場合、初期化は成功します。

if let twoSocks = CartItem(name: "sock", quantity: 2) {
    print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// "Item: sock, quantity: 2" と出力

CartItem インスタンスを quantity 値 0 で生成しようとすると、CartItem のイニシャライザは初期化を失敗させます。

if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
    print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
    print("Unable to initialize zero shirts")
}
// "Unable to initialize zero shirts" と出力

同様に、CartItem インスタンスを空の name 値で生成しようとすると、スーパークラス Product のイニシャライザは初期化を失敗させます。

if let oneUnnamed = CartItem(name: "", quantity: 1) {
    print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
    print("Unable to initialize one unnamed product")
}
// "Unable to initialize one unnamed product" と出力

failable イニシャライザをオーバーライド

スーパークラスの failable イニシャライザを、他のイニシャライザと同じように、サブクラスでオーバーライドすることができます。あるいは、スーパークラスの failable イニシャライザをサブクラスの nonfailable イニシャライザでオーバーライドすることができます。スーパークラスの初期化は失敗することがある一方で、初期化に失敗することが無いサブクラスを定義できるようになります。

スーパークラスの failable イニシャライザを、サブクラスの失敗することが無いイニシャライザでオーバーライドする場合、スーパークラスのイニシャライザにデリゲートする唯一の方法は、スーパークラスの failable イニシャライザの結果を強制的にアンラップすることです。

NOTE
nonfailable イニシャライザで failable イニシャライザをオーバーライドすることはできますが、その逆はできません。

次の例では、クラス Document を定義しています。このクラスは、空でない文字列値または nil(空文字列にはできない)の name プロパティで初期化されるドキュメントをモデル化しています。

class Document {
    var name: String?
    // このイニシャライザは name 値 nil のドキュメントを生成
    init() {}
    // このイニシャライザは name 値が空でないドキュメントを生成
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

次の例では、Document のサブクラス AutomaticallyNamedDocument を定義しています。サブクラス AutomaticallyNamedDocument は、Document の designated イニシャライザの両方をオーバーライドしています。インスタンスが名前無しで初期化された場合や、init(name:) イニシャライザに空文字列が渡された場合に、これらのオーバーライドは AutomaticallyNamedDocument インスタンスの name が "[Untitled]" の初期値を持つようにしています。

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 は、スーパークラスの failable イニシャライザ init?(name:) を、nonfailable イニシャライザ init(name:) でオーバーライドしています。AutomaticallyNamedDocument はスーパークラスと異なる方法で空文字列を扱うため、イニシャライザは失敗する必要が無く、失敗することが無いイニシャライザにすることができます。

サブクラスの nonfailable イニシャライザの実装の一部として、スーパークラスの failable イニシャライザを呼び出すために、イニシャライザで強制的なアンラップを使うことができます。例えば、次のサブクラス UntitledDocument は常に名前を "[Untitled]" として、初期化の間にスーパークラスの failable イニシャライザ init(name:) を利用しています。

class UntitledDocument: Document {
    override init() {
        super.init(name: "[Untitled]")!
    }
}

このケースでは、スーパークラスの init(name:) イニシャライザが空文字列の名前で呼び出された場合、強制的なアンラップの操作は実行時エラーという結果になります。けれども、文字列定数で呼び出されているため、イニシャライザは失敗せず、このケースで実行時エラーは発生しません。

failable イニシャライザ init!

一般的には、init キーワードの後にクエスチョンマーク (init?) を置いて、適切な型のオプショナルインスタンスを生成する failable イニシャライザを定義します。あるいは、failable イニシャライザを定義することができます。無条件にアンラップされた適切な型のインスタンスを生成する failable イニシャライザを定義することができます。こうするには、init キーワードの後にクエスチョンマークではなく、エクスクラメーションマーク (init!) を置きます。

init? から init! にデリゲートすることができ、その逆も可能で、init? を init! でオーバーライドすることができ、その逆も可能です。また、init から init! にデリゲートすることで init! イニシャライザが初期化に失敗するかのアサーションを引き起こしますが、init から init! にデリゲートすることも可能です。


Portions of this page are translations based on work created and shared by Apple and used according to terms described in the Creative Commons Attribution 4.0 International License.