web-dev-qa-db-ja.com

延期ステートメントと復帰直前のステートメントの違いは何ですか?

これの違いは何ですか:

_ = navigationController?.popViewController(animated: true)

defer {
    let rootVC = navigationController?.topViewController as? RootViewVC
    rootVC?.openLink(url: url)
}
return

この:

_ = navigationController?.popViewController(animated: true)

let rootVC = navigationController?.topViewController as? RootViewVC
rootVC?.openLink(url: url)
return

AppleのSwiftガイドラインによると、「コードの実行が現在のコードブロックを離れる直前に、deferステートメントを使用して一連のステートメントを実行します。」、しかしそれでもまだわかりません。

16
Li Fumin

延期ステートメントと復帰直前のステートメントの違いは何ですか?

世界のすべての違い。 deferステートメントが実行されますafter return!これにより、他の方法では達成できないことを達成できます。

たとえば、値を返し、thenで値を変更できます。 Appleはこのトリックを非常に定期的に利用しています。たとえば、カスタムシーケンスの記述方法を示すシーケンスドキュメントのコードを次に示します。

struct Countdown: Sequence, IteratorProtocol {
    var count: Int

    mutating func next() -> Int? {
        if count == 0 {
            return nil
        } else {
            defer { count -= 1 }
            return count
        }
    }
}

あなたがそれを次のように書いた場合

            count -= 1
            return count

...それは壊れるでしょう。 countをデクリメントしてそれを返すのではなく、countを返してデクリメントする必要があります。

また、既に指摘したように、deferステートメントはhow終了しても実行されます。また、現在のscopeを終了しても機能します。これは、returnをまったく含まない可能性があります。 deferは、関数本体、whileブロック、if構成、doブロックなどで機能します。単一のreturnは、そのようなスコープを終了する唯一の方法ではありません!メソッドに複数のreturnが含まれている可能性があります。エラーが発生したり、breakなどが発生したり、単にスコープの最後の行は当然です。 deferは、可能なすべてのケースで実行されます。可能なすべての出口をカバーするように「手作業で」同じコードを書くと、エラーが発生しやすくなります。

33
matt

あなたの例では実際には違いはありませんが、これを見てください:

_func foo(url: URL) -> Int
    let fileDescriptor : CInt = open(url.path, O_EVTONLY);
    defer {
      close(fileDescriptor)
    }
    guard let bar = something1() else { return 1 }
    guard let baz = something2() else { return 2 }
    doSomethingElse(bar, baz)
    return 3
}
_

close(fileDescriptor)は、関数が返す行に関係なく常に実行されます。

16
vadian

deferステートメントは、実行が最近のスコープを離れる直前にコードを実行するために使用されます。

例えば:

func defer()  { 
 print("Beginning") 
 var value: String? 
 defer { 
    if let v = value { 
        print("Ending execution of \(v)")
    } 
 } 
 value = "defer function" 
 print("Ending")
}

印刷される最初の行は次のとおりです。

印刷される2行目:終了

そして、印刷される最後の行は、次のとおりです。defer関数の実行を終了しています。

7
H S Progr

deferを使用すると、関数の最後での条件付きクリーンアップを回避できます。

この例を考えてみましょう:

class Demo {
    var a : String
    init(_ a:String) {
        self.a = a
    }
    func finish() {
        print("Finishing \(a)")
    }
}

func play(_ n:Int) {
    let x = Demo("x")
    defer { x.finish() }
    if (n < 2) {return}
    let y = Demo("y")
    defer { y.finish() }
    if (n < 3) {return}
    let z = Demo("z")
    defer { z.finish() }
}

play(1)
play(2)
play(3)

関数playは、パラメーターに応じて1つ、2つ、または3つのDemoオブジェクトを作成し、実行の最後にそれらに対してfinishを呼び出します。関数が途中から戻った場合、deferステートメントは実行されず、作成されなかったオブジェクトに対してfinishは呼び出されません。

これの代わりに、オプションを使用する必要があります:

func play(_ n:Int) {
    var x:Demo? = nil
    var y:Demo? = nil
    var z:Demo? = nil
    x = Demo("x")
    if (n >= 2) {
        y = Demo("y")
    }
    if (n >= 3) {
        z = Demo("z")
    }
    x?.finish()
    y?.finish()
    z?.finish()
}

このアプローチでは、すべての宣言が一番上に配置され、後でオプションをアンラップする必要があります。一方、deferを含むコードでは、初期化を行うコードの近くにクリーンアップコードを記述できます。

4
dasblinkenlight