2
1

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 1 year has passed since last update.

SwiftAdvent Calendar 2022

Day 18

if letを使わずにOptionalを使う

Posted at

はじめに

swiftではOptional型は必ずと言っていいほど使うものですが、どうもコードがまどろっこしくなりがちだと思ってました。これはif let構文をいちいち書いてたからですが、調べてみるともう少しシンプルに書ける場合もあることがわかったのでまとめてみます。

環境

xcode version 14.1でテストしました。
swiftのバージョンは5.7.1です。

1.Optionalとは

Optionalとは、簡単に言ってしまえば任意の型に対してnilを設定するためのものです。

例えばjavaでは、任意の型に対して直接nullを設定できます。

java
String str1 = null;

しかしswiftではnilを設定できません。
コンパイルエラー('nil' cannot initialize specified type 'String')が発生します。

swift
var str1: String = nil // Error

なぜこのような仕様になっているかというと(多分ですが)、例えば以下のようなコードだと、

java
String str1 =  "ABC";
if(特定の条件){
  str1 = null;
}
int length = str1.length();

最後の行でエラーが発生してしまうことがあるからです。
javaで有名な?NullPointerExceptionですね。

このように、いつnullになっているか分からないと言った状況を避けるために、普段はnilを設定できないようにしておき、特定の条件の時だけnilを設定できるようにしよう、という仕様にしたのだと思います。

swiftでnilを設定できるようにするにはクェスチョンマーク(?)を付けて以下のように宣言します。
これで、nilでも文字列でもどちらでも設定できます。

swift
var str1: String?

str1 = nil
str1 = "it's holiday!"

ちなみにこの宣言は以下のように書いたのと同じです。

var str1: Optional<String>

Optional型の中にString型のメンバーが入っているということになります。

そして中身を取り出すには、if let構文を使う事になります。

var str1: String? = "it's holiday!"
print("str1: \(str1) (type: \(type(of: str1)))")

if let str2 = str1 {
  print("str2: \(str2) (type: \(type(of: str2)))")
}
実行結果
str1: Optional("it\'s holiday!") (type: Optional<String>)
str2: it's holiday! (type: String)

str1はOptional型、str2はString型になっている事に留意して下さい。

swiftツアー(英語です)に書いてあるのは基本的にここまでで、これに従うと中身が必要になる度に毎回if let構文を書くことになるのですが、それだと冗長で読みにくいコードになってしまうわけで、何とかしようというのがこの記事の趣旨です

個人的な意見になりますが、if let構文は分かりにくいと思っています。
単独でlet文を使った場合は定数の宣言なのに、if文の中に書くと意味が変わってくるというのが分かりづらくて、もっといいsyntaxなかったのかと毎回もやもやしてしまうのも苦手に感じる理由でしょうか。

ちなみにですが、無理矢理if letを使わないで書くとこんな感じになります。
長くなるしあまり良くはないですね。

var found = false
var str7 = ""
switch str1 {
case .some(let str) :
  str7 = str
  found = true
default :
  found = false
}
if found {
  print("str7: \(str7) (type: \(type(of: str7)))")
}

本題に戻りましょう。

2.例

例として以下のようなコードを考えてみます。

class SomeClass {
  var str = "ABC"
  var list = [1,2,3,4]
}

var someClass : SomeClass? = nil
if 特定の条件 {
  someClass = SomeClass()
}
...

この後にsomeClassのプロパティにアクセスする方法を考えます。

3. if letを使わないパターン

3.1 とりあえず先送りにする

optionalチェーンと言われるもので、?を変数の後ろに付けます

let str3 = someClass?.str
print("str3: \(str3) (type: \(type(of: str3)))")
実行結果
str3: Optional("ABC") (type: Optional<String>)

SomeClassのstrプロパティはString型ですが、この演算子を使うことでOptional型になっている事に注意して下さい。
結局Optionalじゃないかと言われるかもしれませんが、この後で処理していきます。

3.2 デフォルト値がある場合

nilの場合にデフォルト値で処理可能な場合には、演算子??が使えます。
Stringなら空文字列をデフォルトにできることが多いので、例として以下のようになります。

let str2 =  someClass?.str ?? ""
print("str2: \(str2) (type: \(type(of: str2)))")
実行結果
str2: ABC (type: String)

String型になりました。

3.3 比較はそのままできる

Optional型では演算子==が定義されているので、同じかどうかの比較は中身を取り出さなくても直接比較できます。

if someClass?.str == nil {
  print("nil")
}
if someClass?.str == "ABC" {
  print("ABC")
}

nilとの比較だけではなく、中身のStringとも比較できます。

3.4 forループではなくforEachを使う

someClassの配列プロパティにアクセスする場合、forループを使おうとするとif letを使う事になります。

if let list = someClass?.list {
  var sum = 0
  for element in list {
    sum += element
  }
  print("sum: \(sum)")
}

代わりに配列のforEach関数を使うとif letを使わないで済みます。

var sum2 = 0
someClass?.list.forEach { element in
  sum2 += element
}
print("sum2: \(sum2)")

3.5 Optionalにメソッドを定義してみる

これはちょっと思い付きですが、Optionalにメソッドを定義して、

extension Optional {
  func ifHasValue(hasValueClosure: (Wrapped) -> Void) {
    if let wrapped = self {
        hasValueClosure(wrapped)
    }
  }
}

if letで書いていた部分をクロージャーとして書けるようにしてみます。

var str8 = someClass?.str

str8.ifHasValue { str in
 print("str: \(str) (type: \(type(of: str)))")
}

if文じゃ無くなってしまうから分かりにくいかな?

まとめ

最後はちょっと強引だったかもですが、if letを書かなくて済むパターンをまとめてみました。
それぞれのパターンはコードとして見かける事はあるのですが、まとまっている記事は見かけなかったので作ってみました。

2
1
2

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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?