2015/04/08更新

[Swift] Optional型(?)とnilでないことを言い張る(!)の使い方を整理してみた

このエントリーをはてなブックマークに追加            

こんにちは、@yoheiMuneです。
ここ2ヶ月くらいSwiftでiPhoneアプリの制作を行っています。str!str?の違いを使い分けがなかなか大変だなぁと感じました。今日はその使い分けとか、なぜ使い分ける必要があるのかなど、ブログ纏められたらいいなと思います。

画像

Special Thanks to https://flic.kr/p/cd6XnL





Swiftの基本

Swiftの基本は以前に以下のブログを書きましたので、ご覧いただけましたら幸いです。特に変数定義の部分とか不安な方は見ていただけると、何かの助けになるかもしれません。

[Swift] Swiftの言語仕様に入門する



Swiftの変数は基本的にnilを受けつけない

Swiftの変数は、デフォルトではnilを受けつけないように設計されています。例えば以下のようなコードを書くと、コンパイルエラーです。
var str = "Hello Swift"
str = nil // ここでコンパイルエラー
予期せぬnilのために本番アプリがクラッシュすることも多く、それを防ぐための仕様のようです。上記のコードでstr変数にnilを代入したい場合には以下のように書きます。
var str:String? = "Hello Swift"
str = nil
さてここで出てくるのが?という記号です。最初見たときには「なんだこの不思議な構文」とか「変なの〜」と思っていました。しかしここに出てくる?と後述の!を使いこなさないとiPhoneアプリを書ききることができませんでしたので、覚える必要がありますw。



nilを受け付ける「?」を用いたOptional型の変数

前述の通りSwiftの変数はnilを受けつけない思想で設計されています。nil(=値がない)状態を変数に持たせる場合には、以下のように行う必要がありました。
var str:String?  // この時点ではstrはnil
str = "Hello Swift"
こんな単純な例であれば良いですが、少し混乱するのが関数の引数や戻り値にオプショナル型が入る場合です。以下のような感じで使います。
// まずは基本形(引数も戻り値もnilを受けつけない)
func calcSome1 (val:Int) -> Int {
    return val + 2
}
let result1 = calcSome1(2)
// 引数にnilを許容する場合
func calcSome2 (val:Int?) -> Int {
    
//    return val + 2 // valがnilの可能性があるのでコンパイルエラー
    
    // 例えば以下のようにvalの存在チェックする必要がある
    if let aValue = val {
        return aValue + 2
    } else {
        return 2
    }
}
let result2_1 = calcSome2(2)
let result2_2 = calcSome2(nil)
// 戻り値にnilを許容する場合
func calcSome3 (val:Int) -> Int? {
    // なんらかの理由でnilを返すことがある場合は、戻り値はオプショナルにする
    if val == 2 {
        return nil
    }
    return val + 2
}
let result3_1 = calcSome3(2) // result3_1はOptional型になり、値はnil
let result3_2 = calcSome3(4) // result3_2はOptional型になり、値は6
「Optional型はnilを許容する変数である」と認識することで、おおよそ実装できると思います。そして1点注意したいことが、上記の最後の例でもあるように、関数の戻り値によって変数が自動的にOptional型になる場合があるということです。上記の場合、result3_1result3_2はOptinal型であり、以下のようにするとコンパイルエラーになります。
// オプショナル型変数を、非オプショナル型の引数に与える
calc1Some(result3_1) // コンパイルエラー
例えば実践的な例で言うと、以下はOptional型の値を受け取ります。
let icon = UIImage(named: "icon.png")
関数の戻り値がOptional型なのか、その結果変数はOptional型になるのか、それぞれ気をつけておく必要があります(忘れていてもコンパイルエラーで教えてくれますが)。



Optional型変数でnilでないことを言い張る「!」

続いて?と同じく不思議なのが!です。オプショナル型の変数に!を付けることで「私はnilではない」と言い張ることができます。例えば以下のような文字列の連結の場合に、!が必要です。
var name: String?
name = "Yohei"
var greeting = "Hello " + name!
// var greeting = "Hello " + name  // 「!」がないとコンパイルエラー
また別の例として、Optional型の変数を引数として受けつけない関数を呼び出す場合にも!が必要です。
// 引数は非Optional型でnilを受けつけない
func getMessage (name: String) -> String {
    return "Hello " + name
}

// Optional型変数でgetMessageを呼び出す
var name: String?
name = "Yohei"
var message = getMessage(name!)
// var message = getMessage(name) // 「!」がないとコンパイルエラー
このようにOptional型変数を、非Optional型しか受け付けてくれない場面で使えるようにするために!を使います。

!は便利ですが、もし対象の変数の値がnilだった場合には、実行時エラーになります。私も開発中に何度かnilで爆死することがありました・・。
// 引数がOptional型だが、関数内では「name!」と扱っていて危険
func getMessage (name: String?) -> String {
    return "Hello " + name!
}

// 値がnilの状態でgetMessgeを呼び出す
// getMessage内で「name!」として扱っているので、実行時エラーが出る。
var name: String?
var message = getMessage(name)
上記を防ぐために、対象の変数がnilか否かで処理を分岐します(Swiftのチュートリアルで良く見るやつです)。
// nameの値がnilか否かで処理を分岐する
func getMessage (name: String?) -> String {
    if let aName = name {
        return "Hello " + aName
    } else {
        return "Hello no name"
    }
}
ここらへんの書き方はSwift特有の方言なので、慣れるまではなかなか大変です。さて以降は実戦における3つの事例を紹介したいと思います。



実践例1:クラス定義

まず1つ目はクラスを定義する場合です。クラスに保持するプロパティをOptional型にすべきか、非Optional型にすべきか悩みどころです。
// 個人的な設計思想ですが、必須プロパティについては非Optinal型にして
// 任意のプロパティはOptional型にするのがいいかなーと思っています。
class Animal {
    var id = 0                // 必須
    var name = ""             // 必須
    var description: String?  // 任意
    
    // 初期化関数
    // descriptionは任意なので、引数のデフォルト値をnilにしています
    init (id:Int, name:String, description: String?=nil) {
        self.id = id
        self.name = name
        self.description = description
    }

    // descriptionの有無で返却値を切り替えます
    func getInformation () -> String {
        if self.description != nil {
            return "id=\(id), name=\(name), description=\(description!)"
        } else {
            return "id=\(id), name=\(name)"
        }
    }
}

// descriptionを指定する場合
var animal1 = Animal(id: 1, name: "dog", description: "running by four legs")
var info1 = animal1.getInformation()

// descriptionを指定しない場合
var animal2 = Animal(id: 2, name: "cat")
var info2 = animal2.getInformation()
Optional型を多用するとコードの様々な場所で?!if let xxx = yyy {}といったコードが散乱して大変なので、Optional変数は必要最低限で使うのが良いのではと感じています。



実戦2:オプショナルチェーン

Optional変数を用いたメソッドチェーンで?を使うことで、いい感じにnilチェックせずに安全にOptional変数を使う事ができます。
var animal1 = Animal(id: 1, name: "dog", description: "running by four legs")
animal1.description?.utf16Count  // 20

var animal2 = Animal(id: 2, name: "cat")
animal2.description?.utf16Count  // nil
descriptionに値が設定されている場合はそれが使われ、nilの場合にはnilが返却されます。nilチェックなしにメソッドチェーンが使えるところはObjective-Cでもありましたがなかなか便利な特徴です。



実戦3:型キャスト

最後に型のキャストです。AnyObjectからStringなどにダウンキャストする場合に、受ける変数がOptional型の場合はキャスト時にas?を使います。
class Music: NSObject, NSCoding {

    var title:String?
    var ranking = 0
    
    override init() {}
    
    convenience required init(coder aDecoder: NSCoder) {
        self.init()
        title = aDecoder.decodeObjectForKey("title") as? String // Optional型へのキャスト
        ranking = aDecoder.decodeObjectForKey("ranking") as Int // 非Optional型へのキャスト
    }
}
asas?の違いを理解するのに最初は戸惑いましたが、慣れるといい感じに使えますー。



参考資料

Swiftでの初めての開発の際に、以下の記事を参考にしました。ありがとうございます。

The Swift Programming Language | Apple



最後に

SwiftはObjective-Cに比べてかなりライトに記述できますが、?!などは言語の方言としてなかなか強いなぁと思いました。といっても、それらに慣れればとても使いやすい言語だと思います。Swiftに関する書きたい記事はいっぱいあるので、少しずつブログで紹介できたらと思います。

本ブログでは、フロントエンドに関する情報を中心に発信していきます。気になった方はぜひ、本ブログのRSSTwitterをフォローして頂けると幸いです ^ ^。

最後までご覧頂きましてありがとうございました!





こんな記事もいかがですか?

RSS画像

もしご興味をお持ち頂けましたら、ぜひRSSへの登録をお願い致します。