6
4

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.

IUOについて誤解していたこと

Last updated at Posted at 2023-10-07

今回は、僕がSwiftのIUOについて誤解していたことについてまとめます。
同じ勘違いをしている方が他にもいると思うので、学んだことを共有しようと思います。

問題(1)

ご存知の通り、IUOは型名に!をつけた型です。

var text: String!
text = "Hello"
print(text.count)  // 5

それでは、次の例ではどうなるでしょうか?

var text: String!
text = "Hello"
print(text)  // 何と表示される?

Helloと表示されると思うかもしれません。
それは不正解で、Optional("Hello")と表示されます。

問題(2)

ご存知の通り、IUOは型名に!をつけた型です。

var text: String!
print(text.count)  // クラッシュ

上の例では、値をセットする前にアクセスしてしまっているので、クラッシュします。
それでは、次の例ではどうなるでしょうか?

var text: String!
print(text)  // どうなる?

これも値をセットする前にアクセスしているので、クラッシュすると思うかもしれません。
それは不正解で、nilが表示されます。

そもそもIUOとは?

String?String!の違いは何でしょうか? 今まで僕はこう考えていました。

  • String? ... 文字列かnilのどちらかが入っているという直和型(Optional)。
  • String! ... 必ず文字列が入っていると確信しているのでnilにはならない。

しかし、これは大きな誤解です。
Swift.orgでは、IUOは次のように説明されています。

You can think of an implicitly unwrapped optional as giving permission for the optional to be force-unwrapped if needed.
訳:IUOは、必要であれば強制的にアンラップされることを許可していると考えるといい。

この一文が全てを物語っているのですが、分かりやすくコードを使って説明しましょう。

IUOはオプショナル型

そもそも、String?String!も同じオプショナル型です。

var a: String? = "test"
var b: String! = "test"
print(a)  // Optional("test")
print(b)  // Optional("test")

つまり両者のデータ構造には、何の違いもありません。

?!の違いはどこにあるのか

String?String!の間に違いが現れるのは、メンバにアクセスする時だけです。

var a: String? = "test"
var b: String! = "test"
print(a?.count)  // Optional(4)
print(a!.count)  // 4
print(b.count)   // 4

String?ではメンバにアクセスする時、?.!.の2通りの方法がありますよね。!.は強制アンラップといって、aに値が入ってなければクラッシュさせることで、オプショナルを解除(アンラップ)します。
この!.!を省略できるようにしたものがIUOです
これが、IUOが「Implicitly Unwrapped Optional(暗黙的アンラップ型)」と名付けられた所以です。

必要であればアンラップされる

既に述べたSwift.orgの引用文にある「必要であれば」という言葉も、IUOの挙動を理解する上で重要です。

You can think of an implicitly unwrapped optional as giving permission for the optional to be force-unwrapped if needed.
訳:IUOは、必要であれば強制的にアンラップされることを許可していると考えるといい。

以下のコードを使って説明します。

var a: String! = "hoge"
var b: String? = a  // b=Optional("hoge")  されない
var c: String = a   // c="hoge"  される
func f(s: String) { print(s) }
print(a)  // Optional("hoge")  されない
f(s: a)   // hoge  される

aの値が使われている2, 3, 5, 6行目をそれぞれ見ていきましょう。

  • 2行目、bに代入するときはOptionalのままでもいいので、アンラップされません。
  • 3行目、cに代入するときはOptionalのままではいけないので、暗黙的に強制アンラップされます。
  • 5行目、printの引数はOptional可なので、アンラップされません。
  • 6行目、fの引数はOptionalではないので、暗黙的に強制アンラップされます。

この例から、アンラップが必要ない場面では、IUOのアンラップは行われないことが分かります。

初期値なしの非オプショナル型

IUOがオプショナルで、暗黙的に強制アンラップしているという事実から、必要もなくIUOの使うのは避けた方がいいです。
例えば、イニシャライザで初期化されるようなプロパティは初期値なしの非オプショナル型を使うといいです。

 class Main {
-    let text: String!
+    let text: String

     init(text: String) {
         self.text = text
     }
     
     func main() {
         print(text.count)  // 5
     }
 }
 
 Main(text: "Hello").main()

初期値なしの非オプショナル型は、もし初期化漏れなどで、値が入っていないままアクセスされる危険があるときは、実行する前からエラーを出して教えてくれるので便利です。
下の例のように、関数内のローカル変数でも使えます。

let num = 123
let r: String
if num.isMultiple(of: 2) {
    r = "偶数"
} else {
    r = "奇数"
}
print(r)  // 奇数

もしこのif文の条件網羅やrの初期化に漏れがあった場合、コンパイラが実行前エラーで教えてくれます。
IUOを回避する一つのパターンとして覚えておきたいです。

6
4
0

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
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?