Edited at

Optional型の勘所

More than 3 years have passed since last update.

(書いている途中で書きたかったことがだいたい書いてある記事が公表されたので、方向性を変えています)


要旨


  • 代入先がOptional型なら、optional chainingを代入できる


  • if letもいいが??も便利


  • as?を使えば色々と楽

  • Implicitly Unwrapped Optional型をnilチェックしてはならない


内容


趣旨

 Swiftをいじり倒してみると、思った以上にオプショナル型が強力であって「書きたいことを書ける」と分かってきました。「オプショナル型のなにが便利なんだ?」「どう使えばいいんだ?」「if let書きまくりで面倒なだけなんだが?」という人の参考になるものを書いてみたいなと。

 ただし、以下の内容 (特に最後の部分) は私が勝手にそう考えているだけなので、あくまで参考程度にしたほうがよいかもしれません。

 なお純粋な仕様についてはこちらが詳しいので、解説しません。


代入先がOptional型なら、optional chainingを代入できる

 仕様をちゃんと読むと書いてあるのですが、Optional型のチェーンは「もしそれがnilなら、その式はnilを返す」というものです。

// hogeはInt?型

var hoge: Int? = 10

// hoge?... はnilを返す(かもしれない)
// そこでfugaもInt?型になる
let fuga = hoge?.advancedBy(10)

 ということは、代入先をオプショナル型にしておけば、いちいちforced unwrappingしないまま次々に代入していけるわけです。

// いちいちforced unwrappingする場合

// もしnilだとエラーになる
var goho: Int
gogo = fuga!.advancedBy(10)

// 代入先もオプショナルなので、これで通る
var hoho: Int?
hoho = fuga?.advancedBy(10)

 nilチェックする必要がないとき、適切に先送りして処理を単純化できます。


if letもいいが??も便利

 nilかもしれない値を取り出すとき、if let/var hoge = ... else ...などと書くことができます。処理を分岐させるなら、素直にこれを使うことになりますが、ただオプショナル型を非オプショナル型に変換するだけなら??で十分ということもあります。

// fugaはnilかもしれないので、fuga?.advancedBy(10) はnilかもしれない

// そのときは0を返す
let result = (fuga?.advancedBy(10)) ?? 0
// resultはnilになり得ないので、Int型になる

 文脈によって便利なものを使えばOKですね。


as?を使えば色々と楽

 as?は「キャストできればキャストし、無理ならnilを返す」というものです。

 色々と使い道がありますが、たとえば中身の予想されるNSArrayやNSDictionaryなどをSwiftらしく使えます。

import Cocoa

// 何らかの理由で、[String:[Int]] が戻り値と予想できる関数
func testFunc() -> NSDictionary {
return ["test": [100,200,300], "text": [10]]
}

if let d = testFunc() as? [String:[Int]] {
// d: [String:[Int]] であることは確定
// したがって d[0] と書くことはできない
}

 JSONや.plistの処理あたりで使えるかもしれません。


Implicitly Unwrapped Optional型をnilチェックしてはならない

 Int!型やString!型について、「nilかも知れないので、チェックしなければならない」とか、「中身がnilでも構文上エラーにならず、稼働時に落ちるので、なるべく使わないほうがよい」という人もいます。

 しかし考えてみると、これは全く逆であって、「Implicitly Unwrapped Optional型は “nilであってはならない変数” に使うものであり、nilである場合のエラーを回避してはならない」「絶対に必要なものは!にするほうがよい」というべきです。

 たとえばXcodeでは、@IBOutlet ... UITextField! などと入力されます。これは決して「nilかも知れないので毎回注意すべし」という意味ではありません。インスタンス変数なので理屈上はnilかも知れないが、実際にnilだと困るので、絶対にnilではないと保証されていると考えるべきです。

 そのような不可欠のインスタンス変数に?を付けてしまうと、ミスの温床になるでしょう。

// 何らかの理由で、viewDidLoadなどで設定すべき変数

var hogeManager: HogeManager?

override func viewDidLoad() {
super.viewDidLoad()

// hogeManager = HogeManager() を書き忘れても問題なく動いてしまう
hogeManager?.requestHoge()
}

 Int!型やString!型についても、「理論上はnilかも知れないが、仮にnilのままであればミスである」という場合に使うべきです。仕様上nilかもしれない場合には?型を使うべきであり、Implicitly Unwrapped Optional型を使う以上、nilチェックによりエラーを回避すべきではありません。エラーを発生させてミスを探すべきです。

// 何らかの理由でイニシャライザのないクラス

class Hoge {
// タイトルとナンバーが必須だが、その設定は人力で確保しなければならない
var title: String!
var number: Int!
func hogeInt() -> Int {
// title = nilだとエラーになる構文
return (title.toInt() ?? 0) + number
// しかしtitle: String!型なので警告されない

// もしtitle: String?型なら、Xcodeの自動補正でtitle?.toInt()と入力される
// その場合 title = nil でも式が正常に評価されてしまう
}
}

let hoge = Hoge()

// titleとnumberはnilであってはならない
// 書き漏らすとエラーになるので発覚する
hoge.title = "5050"
hoge.number = 4649
hoge.hogeInt() // -> 9699

 素の型・?型・!型を使い分けることで、変数の性格を適切に表現することができます。