nil大特集

Swiftのnil関係の話だけを収集するプロジェクト


実はOptional型でした

実はOptional型でした

String?型とかInt?型とかがあるわけではない。そこにあるのはOptional<T>だ。Optionalと書くのがめんどくさい人のためにT?という書き方があるのであってT?型はない。

//let str: String? = nil
let str: Optional<String> = nil

//let number: Int? = nil
let number: Optional<Int> = nil

StringやIntが拡張されてnilが入れるようになったのではなく、Optionalというやつにnilと一緒に取り込まれた感じ。Optionalの定義を見てみると……

enum Optional<T> : Reflectable, NilLiteralConvertible {
    case None
    case Some(T)

    /// Construct a `nil` instance.
    init()

    /// Construct a non-\ `nil` instance that stores `some`.
    init(_ some: T)

    /// If `self == nil`, returns `nil`.  Otherwise, returns `f(self!)`.
    func map<U>(f: (T) -> U) -> U?

    /// Returns a mirror that reflects `self`.
    func getMirror() -> MirrorType

    /// Create an instance initialized with `nil`.
    init(nilLiteral: ())
}

extension Optional : DebugPrintable {

    /// A textual representation of `self`, suitable for debugging.
    var debugDescription: String { get }
}

Optional<T>はenum型であり、NoneとSome(T)という2つから成り立っていることがわかる。そしてなにより、Optional<T>型が持つメソッドは非常に少ない。Optional<T>型が持つメソッドならともかく、実際に使いたいところのT型のメソッドはOptional型は持っていない。Optional<T>型という箱から取り出して初めてT型のメソッドを使用することができる。hoge?.method()みたいに余計に「?」を書かないとメソッドが使えない理由はこれである。
Xcodeで出されるOptional型のメソッド名の補完は、Optional型のメソッドではなくT型のメソッドが出ているので分かりづらいが、Optional型はT型のメソッドは使えない。これ大事。

var hoge: Bool? = true

/*
Optional<Bool>なので比較には使えない…
if hoge {
    println("hoge")
}
*/

/*
?をつけてもだめ
if hoge? {
    println("hoge")
}
*/

//これだとfalse時にも実行されちゃうし……
if (hoge != nil) {
    println("hoge")
}

//これならfalseとnilのときは実行されない
if let hoge = hoge where hoge {
    println(hoge)
}

Optional<Bool>もこのざまだ。

というわけでOptional<T>という箱を割って中のT型の物体を取り出す方法を続けてどうぞ。

Optional型から中身を取り出す方法(前半)

Optionalから中身を取り出すOptional型をアンラップする方法は4種類ある。

  1. 後ろに?をつける(Optional Chaining)
  2. if文を使う(Optional Binding)
  3. 後ろに!をつける(Forced Unwrapping)
  4. 4つ目は後半で

このうち安全なのは?をつけるやつとif文を使うやつである。

後ろに?をつける
import UIKit

var button:UIButton?
button?.titleLabel?.text = "1"
println(button?.titleLabel?.text)	//nil

button = UIButton()
button?.setTitle("1",forState:UIControlState.Normal)
println(button?.titleLabel?.text)	//Optional("1")
button?.titleLabel?.text = "2"
println(button?.titleLabel?.text)	//Optional("2")

3行目でOptional<UIButton>型変数を宣言した。UIButtonのインスタンスが入っていない、要するにnilが潜んでいる変数である。
4・5行目ではフィールドtitleLabelのフィールドtext、要するにUIButtonの中のUILabelの中のフィールドを使用しようとしている。もちろんUIButtonもその中のUILabelも存在しない。
が、間に?を挟む、Optional Chainingを使用することによりエラーにはならない。用心深くそいつをチェックし、nilであるとわかれば(nilを返して)それ以上の処理は行わない。強引に代入するとか呼び出したりとかはしないので安全だということ。

とはいえこの方法ではOptional<T>は最初から最後までOptional<T>のままである(nilを返す可能性がある以上返り値はOptionalだ)。時にはOptionalの殻を完全に破り捨てたい時もある。破り捨てないと関数の引数のとこに入れられないとか。
そんな時は後ろの3つを使おう。

if文を使う(Optional Binding)

if文を使えば、if文の中だけOptional<T>ではなくTとして使用することが可能である。

var str: String?
var number: Double?

//「strはnil」
if var str: String = str {
    if let number: Double = number {
        println("\(str) \(number)")
    } else {
        println("strはnilじゃないけどnumberはnil")
    }
} else {
    println("strはnil")
}

str = "Swift"

//「どちらかがnil」
if var str = str, let number = number {
    println("\(str) \(number)")
} else {
    println("どちらかがnil")
}

number = 1.2

//「Swift 1.2」
if let str2 = str, number2 = number {
    println("\(str2) \(number2)")
} else {
    println("どちらかがnil")
}

こいつを使えばOptional<T>型ではなくなるので、T型を要求する関数に渡すとかそういうことができるようになる。しかも安全に。

後ろに!をつける(Forced Unwrapping)

後ろに!をつけてもOptionalを外すことができ、引数とかに入れる事も可能である。ただしForcedというくらいだから強引である。中身がnilだった場合実行時に止まる。天地がひっくり返ってもnilでないとわかっているやつにしか使えないということである。

あともう1つ方法があるが、その前に1つ説明をば。

実はImplicitly Unwrapped Optional型でした

Storyboard/Xibから持ってきた部品は次のような変数とリンクされている。

@IBOutlet weak var hogeView: UIView!

UIView!型とか、UILabel!型とか、そんな感じのやつである。
もとい、
ImplicitlyUnwrappedOptional<UIView>型とか、ImplicitlyUnwrappedOptional<UILabel>型とか、そんな感じのやつである。
定義を見てみるとだいたいOptional型と同じである。

enum ImplicitlyUnwrappedOptional<T> : Reflectable, NilLiteralConvertible {
    case None
    case Some(T)

    /// Construct a `nil` instance.
    init()

    /// Construct a non-\ `nil` instance that stores `some`.
    init(_ some: T)

    /// Construct an instance from an explicitly unwrapped optional
    /// (`T?`).
    init(_ v: T?)

    /// Create an instance initialized with `nil`.
    init(nilLiteral: ())

    /// If `self == nil`, returns `nil`.  Otherwise, returns `f(self!)`.
    func map<U>(f: (T) -> U) -> U!

    /// Returns a mirror that reflects `self`.
    func getMirror() -> MirrorType
}

extension ImplicitlyUnwrappedOptional : Printable {

    /// A textual representation of `self`.
    var description: String { get }
}

extension ImplicitlyUnwrappedOptional : _ObjectiveCBridgeable {
}

nilもT型の物体も入るようになっている。しかもImplicitlyUnwrappedOptional<T>のままでTのメソッドが使える(Optional<T>型のようにOptional Chaining/Bindingでアンラップする必要すらない)。試しにif (Int!型のやつ) is Int { 〜 }ってやってみたらtrueだった。
ただし、中身がnilなのにTのメソッドを使ったりするとアウト。引数として与えてもアウト。nilを代入してもいいし、T型として扱えるけど要注意なのがImplicitlyUnwrappedOptional<T>。ちなみにImplicitlyUnwrappedOptionalにOptional Chainingをすることはできる。一応。

Optional型から中身を取り出す方法(後半)

というわけでアンラップする4つ目の方法は「ImplicitlyUnwrappedOptional<T>型の変数・定数に突っ込む」ということになる。ただしnilの時にそいつをつかうのはやめよう。

??演算子(nil coalescing operator)

nilだったら違うものを代入する??という演算子を利用してなんとかする手もある。

let str: String? = nil
let str2: String? = "hello, nil!"

let coalescedStr = str ?? "nilでした"  //nilでした
let coalescedStr2 = str2 ?? "nilでした"    //hello, nil!

「実はOptional型でした」のまとめ

幸せなOptionalライフをお送りください 〜完〜


戻る