Xcode
iOS
Swift

SwiftでUIImageのimageNamed:でファイルが存在しなかったときに起こる謎

このバグはSwift ver1 がBetaだったときのバグです。現在は発生しません。

Objective-Cで何気なく使っているUIImageimageNamed:メソッド。自分の頭では理解できないことがあったので教えてほしいです。

imageNamed:メソッド、プロジェクトに入っている画像名を指定してこんな感じ使ってると思います。

UIImage *image = [UIImage imageNamed:@"my_image"];

Swiftだとこんな感じ。

let image:UIImage = UIImage(named:"my_image")

御存じだと思いますが、このimageNamed:ファイルが見つからないとnilを返しますね。

Objective-Cだと、ファイルが見つからなかった場合は何も表示されません。ダウンロードしてきた画像は生成できるかnilチェックすると思うのですが、ローカルの画像はついついnilチェックを怠ってしまい、画像が表示されていないことに気づかない可能性も出てきます。アプリの規模が大きくなると画像も増えますし困りもんです。

ではSwiftではどうなるか?

まず前提知識として、Swiftはnilを許容しません。基本的にnilを扱うことができないんです。

かといって全く扱えないのは困るのでnilを使いたい場合はOptional型というものにする必要があるのです(OptionalについてはSwiftのOptionalとType Safetyなどを参照してください)。

こんな感じ。ファイルが存在しなくても安全に処理できます。

var imageView: UIImageView

// ?をつけることでOptional型に
let image:UIImage? = UIImage(named:"my_image")

// Optional Bindingでnilチェック
if let validImage = image {
    imageView = UIImageView(image: validImage)
} else {
    // 画像がなかった場合の処理
}

上記のコードは問題ありません。

次を見てみましょう。

let image:UIImage = UIImage(named:"my_image")
var imageView: UIImageView = UIImageView(image:image)

これで"my_image"ファイルが存在していなかった場合どうなるか?nilが返ってきますね。

なぜこんなことを書いているかというとObjective-Cのコードを何も考えずにSwiftに書き直したらこのコードになったからです。

UIImage *image = [UIImage imageNamed:@"my_image"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];

このObjective-Cを、こうSwiftしてしまった。

let image:UIImage = UIImage(named:"my_image")
var imageView: UIImageView = UIImageView(image: image)

で、書き終わった段階で存在しない"my_image"からインスタンス化しようとする、つまりnilが返ってきて落ちるだろうということに気がついてXcode6(Beta4)で実行してみたところ。。。

何もエラーが出ない。しかもimageインスタンスはnil。Optionalじゃないのに。

println("\(image === nil)") // true

もちろん、これが原因でアプリが落ちるよりも画像が表示されないだけで済む方が良いです。そしてOptionalの存在しないObjective-Cと矛盾は起きない。Optionalを意識せずに書き換えても問題なく動く。

でも、文法的にはimageNamed:の返り値はOptionalであるべきで、UIImage変数もOptionalにするべきなのではないのだろうかと思ったワケです。

分からない。分かる人がいれば教えてほしい!

追記(201407/29)

imageViewのimageプロパティはImplicitly Unwrapped Optionalsなので、nilが代入