しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

Swift2.0のdeferを詳しく調べてみた

Swift2.0で追加されたdeferについて詳しく調べてみました。
deferは下記事でも少し触れています。

llcc.hatenablog.com

deferとは

defer {…}と書くと、そのブロックを抜ける時に{}内の処理が走るという構文です。
下のように書いた場合、1が出力された後に2が出力されます。

func method() {
    defer {
        print(2)
    }
    print(1)
}

method() // 1 → 2の順番に順番に出力される

deferの使いどころ

ブロックの最後に呼ばれてほしい処理がある場合に使うと良さそうです。
ただしJavaのtry-catch-finallyと違い、defercatchの前に呼ばれるのでそこだけ注意が必要です。

enum MyError: ErrorType {
    case Error1
    case Error2
}
do {
    defer {
        print("defer") // 例外が起きても起きなくてもブロックを抜ける前に呼ばれてほしい
    }
    throw MyError.Error1
} catch {
    print("catch")
}
// defer → catch の順に呼ばれる

returnの後に呼ばれるので下のような使い方もできそうです。

class MyClass {
    var value: Int? = 1
    func method() -> Int? {
        defer {
            value = nil
        }
        return value
    }
}
print(MyClass().method()) // → Optional(1)

deferはセットと逆順で呼ばれる

deferはセットした順番と逆順に呼ばれるという特徴があります。
下の形だとSTART → 1 → 2 → 3と呼ばれそうなのですが、実は3,2,1の順番で呼ばれます。
公式ドキュメントにも逆順で呼ばれる事が記載されてるので不具合ではなさそうです。

func method() {
    defer {
        print(1)
    }
    defer {
        print(2)
    }
    defer {
        print(3)
    }
    print("START")
}
method() // → 出力は START → 3 → 2 → 1 の順番

deferは入れ子もできる

下のようにdeferを入れ子にする事もできます。
この場合START → 1 → 2の順番に出力されます

func method() {
    defer {
        defer {
            print(2)
        }
        print(1)
    }
    print("START")
}
method() // → START → 1 → 2

deferのセット前にブロックを抜けると呼ばれない

下のように、deferの前にreturnするとprint(1)は呼ばれません。

func method() {
    return

    defer {
        print(1)
    }
}
method()

別スレッドの処理の完了を待たない

下の場合は"普通の出力" → "defer" → "別スレッド"という順番で呼ばれます。

func method() {
    defer {
        print("defer")
    }
    print("普通の出力")
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
        NSThread.sleepForTimeInterval(1.0)
        print("別スレッド")
    })
}
method()