はじめに
Swiftで開発していてif letやguard let 〜 elseを使ったOptional Bindingをすることがよくあると思います
私が普段、よく使うif letやguard let 〜 elseのOptional BindingのTipsを紹介してみたいと思います
(誰もが知っている当たり前のことしか書いてないかもしれませんが。。)
Optional Bindingの基本
まずは基本として、if letやguard letを使った下記のような書き方はお馴染みだと思います
let optionalString: String? = "aaa"
if let unwrapString = optionalString {
print(unwrapString) //aaa
}
これは、Optional型の変数optionalStringを非Optional型として扱うこと(アンラップ)ができる場合、if letで宣言しているunwrapStringにOptional型が外れたString型の値が代入され、処理がifのスコープ内に入り、unwrapString変数を使うことができます
逆にoptionalStringの値がnilの場合、非Optional型として扱うことができないため、ifのスコープ内には入りません(すなわちunwrapStringも使えません)
if letだとネストされるので下記のようにguardを使って早期リターンで書くケースも多いと思います
let optionalString: String? = "aaa"
guard let unwrapString = optionalString else {
return
}
print(unwrapString) //aaa
また、optionalStringの値が空でない場合という条件を満たしたい場合はwhereを使い下記のように表現できます
let optionalString: String? = "aaa"
if let unwrapString = optionalString where !unwrapString.isEmpty {
print(unwrapString) //aaa
}
optionalString変数を非Optional型に変換したunwrapString変数が空かどうかという条件をwhereに書いています
仮にoptionalStringがnilだった場合は、非Optional型にすることができないため、whereに書いた条件文に入ってくることはありません
Optional Binding Tips
さてここからがTipsとなります
- 例題としてある画像のURL文字列からUIImageを作成し、UIImageViewで表示する場合を考えてみましょう
処理の流れとしては下記とします
1.URL文字であるString型をNSURL型に変換
2.NSURL型をNSData型に変換
3.NSData型からUIImage型にしてUIImageViewを生成する
1,2,3の各型変換処理は全てOptional型が返り値になる処理になります
Tips1
Optional型を全て羅列
下記のようにそれぞれのOptional変換を全てif letで羅列して、各処理の返り値の値が非Optional型として扱うことができる場合、最終的にimage変数にUIImage型の値が生成されUIImageViewに表示することができます
let urlString = "https://pbs.twimg.com/profile_images/3734255592/0b417aa61da0196cfed35889928ee895.png"
if let url = NSURL(string: urlString), data = NSData(contentsOfURL: url), image = UIImage(data: data) {
UIImageView(image: image)
}
仮にNSURL(string: urlString)やNSData(contentsOfURL: url)で非Optional型として扱えないとなった場合、そこで処理が終了しifのスコープ内には入りません
置き換えると下記と同様です
if let url = NSURL(string: urlString) {
if let data = NSData(contentsOfURL: url) {
if let image = UIImage(data: data) {
UIImageView(image: image)
}
}
}
Tips2
先の例題で、画像URLに記載されている画像の拡張子がpngである場合のみUIImageViewに表示するといった要件に変わった場合、下記のようにすることで要件を満たせます
let urlString = "https://pbs.twimg.com/profile_images/3734255592/0b417aa61da0196cfed35889928ee895.png"
if let url = NSURL(string: urlString) where url.pathExtension == "png", let data = NSData(contentsOfURL: url), image = UIImage(data: data) {
UIImageView(image: image)
}
まずif let url = NSURL(string: urlString)で非Optional型であるNSURL型として扱える値かどうかを判定し、NSURL型が返ってきた場合、url変数に代入しwhereでNSURL型の拡張子を調べるメソッドを呼び出し拡張子がpngかチェックしています
その条件が通った場合、さらにurl変数からNSData型を生成し、UIImage型を生成するといった先ほどと同じ流れになります
whereの条件の後にOptional Bindingを使うことができるということです
ちなみにwhereの後に、letをつけないと下記のようにエラーになります
//Binding ended by previous where clause: use let to introduce a new one
if let url = NSURL(string: urlString) where url.pathExtension == "png", data = NSData(contentsOfURL: url), image = UIImage(data: data) {
UIImageView(image: image)
}
Tips3
Optional Bindingと、非Optionalな値をif let内で共存させたいケースってないでしょうか?
例題として、あるURL文字列を保持するurlというOptional型の変数があり、そのファイル名の文字数が5文字より大きければ表示したいとします
処理としては下記とします
1.url変数が非Optionalにできるか
2.urlを/文字で分割
3./文字で分割した最後の部分であるファイル名を取得
4.ファイル名が5文字より大きければ表示
これを直感的に下記のように書いてみます
let url: String? = "http://qiita.com/hachinobu/items/aedac203f42b215e6df4"
if let unwrapUrl = url, elements = unwrapUrl.characters.split("/"), last = elements.last where last.count > 5 {
print(String(last))
}
しかしこれは、コンパイルエラーになります
Initializer for conditional binding must have Optional type, not [SubSequence]
if letやguard letで非Optional型と分かっているものを変数に代入することはできません
(unwrapUrl.characters.split("/")の返り値は非Optional型)
だからといって下記のように多重ネストしたり、分けて書いたりしたくないですよね?(guardを使っても2回return書いたり分けて書くことになる)
let url: String? = "http://qiita.com/hachinobu/items/aedac203f42b215e6df4"
if let unwrapUrl = url {
let elements = unwrapUrl.characters.split("/")
if let last = elements.last where last.count > 5 {
print(String(last))
}
}
さて、これを解決するには2つ方法があります
まず1つめはif letで宣言した変数に明示的に型を書いてあげれば通るようになります
let url: String? = "http://qiita.com/hachinobu/items/aedac203f42b215e6df4"
if let unwrapUrl = url, elements: [String.CharacterView] = unwrapUrl.characters.split("/"), last = elements.last where last.count > 5 {
print(String(last)) //aedac203f42b215e6df4
}
unwrapUrl.characters.split("/")の返り値を格納するelements変数に[String.CharacterView]と型を明示的に書くことでコンパイルが通るようになります
2つめのやり方は、Optional Bindingの特性を利用して、非Optional型と分かっているものをOptional.Some()で包んでOptional型にしてしまうやり方です
let url: String? = "http://qiita.com/hachinobu/items/aedac203f42b215e6df4"
if let unwrapUrl = url, elements = Optional.Some(unwrapUrl.characters.split("/")), last = elements.last where last.count > 5 {
print(String(last))
}
unwrapUrl.characters.split("/")の結果は非Optional型だと分かっているので、Optional.Someで包むことで強引に結果をOptional型にしてOptional Bindingのエラーをパスするやり方です
まとめ
いかがでしょうか
Tipsというほどのものではなかったかもしれませんが、この情報が何かしら役に立てれば幸いです