Can I Swift? その4 – 変わらないプロパティ

in Swift

別のテーマに行こうと思いましたがもう少しだけ変わらない変数をテーマにしたいと思います。

変わらないインスタンス変数 in Objective-C

ローカル変数ほど頻度は高くはありませんが、インスタンス変数も初期値を設定した後インスタンスが消滅するまで値が変わらない場合があります。 ではObjective-Cのインスタンス変数にconstをつけるとどうなるでしょうか。

@implementation Book {
    NSString * const title = @"Default Title";
}

Parse Issue: Expected ';' at end of declaration list 値を設定することができません。 Objective-Cでは変わらないインスタンス変数は実現できないようです。 どうしても実現したい場合はそれに近い方法として@propertyで宣言しreadonly属性をつけるなどとして工夫するしかなさそうです。

変わらないプロパティ in Swift

Swiftのletではどうでしょうか。

class Book {
    let title:String = "Default Title"
}

インスタンス変数に相当するものはSwiftではプロパティとなりますがletをつけても値を設定することが可能です。さらに

class Book {
    let title:String

    init() {
        self.title = "Default Title"
    }
}

と値をinit()(イニシャライザ、初期化子)の中で設定することも可能です。 Swiftのletの方が遥かに柔軟に値を設定することができるようですね。

依存関係をともなう初期化

Swiftで依存関係をともなうような複雑な初期化でもプロパティにletをつけることが可能かどうか。具体的なコードで見てみましょう。

class Author {
    let bestsellerTitle:String = "Bestseller Title"
    let book:Book

    init() {
        self.book = Book(author:self)
    }
}

class Book {
    let title:String = "Default Title"

    init(author:Author) {
        self.title = author.bestsellerTitle
    }
}

クラスAuthorはクラスBookのインスタンスをプロパティとして保持し、init()の中で値を設定しています。 一方クラスBookも自分を生成したクラスAuthorのインスタンスをinit()の引数で受け取っています。

これをコンパイルしようとするとクラスAuthorinit()でエラーになります。 Swift Compiler Error: Variable 'self.book' used before being initialized init()self(インスタンス自身)を参照しているのですが、プロパティbookがまだ初期化されていないためエラーになったようです。

この初期化問題の解決に役立ちそうな記述がドキュメントThe Swift Programming LanguageInitializationの節Optional Property Typesにありました。

SwiftにはOptional Type(オプショナルな型)という非常に重要な考え方があります。 オプショナルな型は通常の型と違い、値がnilをとることを許します。 逆にいえばSwiftでは通常の型で宣言した場合、値がnilをとることを許していません。 これはObjective-Cでは無かった区別で、プログラムがより安全になる反面、その選択には注意が必要になります。 表記方法は簡単で通常の型に対してその後ろに?をつければ(対応する)オプショナルな型になります。

オプショナルな型を使って先ほどのコードを書き直してみましょう。

class Author {
    let bestsellerTitle:String = "Bestseller Title"
    let book:Book?

    init() {
        self.book = Book(author:self)
    }
}

class Book {
    let title:String = "Default Title"

    init(author:Author) {
        self.title = author.bestsellerTitle
    }
}

書き直すといってもlet book:Booklet book:Book?にしただけですが、これによりクラスAuthorinit()でのコンパイルエラーが無くなりました。 何もしなくてもプロパティbookにはnilが設定されるようになったからです。

しかしこのままだと少し不便なことがあります。 プロパティbookをオプショナルな型で宣言してしまったがために、その後bookを参照する度に値がnilかどうか条件判定するはめになります。 bookletがついていてかつinit()で値を設定していますのでnilにはなることは無く、条件判定は全く不要です。

Swiftはこのように文法上はオプショナルな型であることを必要とするものの、処理上はオプショナルな型であることを不要とする場合、?の代わりに!を用いてオプショナルな型を宣言することができます(この型は無条件で開封されるオプショナルな型(Implicitly Unwrapped Optional Type)と呼ぶようです)。 これでわずらわしい条件判定を意識しないで参照できるようになります。 念のため再度書き直してみましょう。

class Author {
    let bestsellerTitle:String = "Bestseller Title"
    let book:Book!

    init() {
        self.book = Book(author:self)
    }
}

class Book {
    let title:String = "Default Title"

    init(author:Author) {
        self.title = author.bestsellerTitle
    }
}

これでOKです。

変わらない変数の話は終わりにして次回はやっと別のテーマに移ろうと思います。 ではでは。

Leave a Reply

*