79
60

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 5 years have passed since last update.

SwiftのOptional BindingのTips

Posted at

はじめに

Swiftで開発していてif letguard let 〜 elseを使ったOptional Bindingをすることがよくあると思います

私が普段、よく使うif letguard let 〜 elseのOptional BindingのTipsを紹介してみたいと思います
(誰もが知っている当たり前のことしか書いてないかもしれませんが。。)

Optional Bindingの基本

まずは基本として、if letguard 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に書いています
仮にoptionalStringnilだった場合は、非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 letguard 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というほどのものではなかったかもしれませんが、この情報が何かしら役に立てれば幸いです

79
60
3

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
79
60

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?