web-dev-qa-db-ja.com

Swiftでコードを1回だけ実行するにはどうすればよいですか?

これまでに見てきた答え( 12 、)は、GCDのdispatch_onceを使用することをお勧めします。

var token: dispatch_once_t = 0
func test() {
    dispatch_once(&token) {
        print("This is printed only on the first call to test()")
    }
    print("This is printed for each call to test()")
}
test()

出力:

This is printed only on the first call to test()
This is printed for each call to test()

しかし、ちょっと待ってください。 tokenは変数なので、簡単にこれを実行できます。

var token: dispatch_once_t = 0
func test() {
    dispatch_once(&token) {
        print("This is printed only on the first call to test()")
    }
    print("This is printed for each call to test()")
}
test()

token = 0

test()

出力:

This is printed only on the first call to test()
This is printed for each call to test()
This is printed only on the first call to test()
This is printed for each call to test()

したがって、tokenの値を変更できる場合、dispatch_onceは役に立ちません。また、tokenを定数に変換することは、UnsafeMutablePointer<dispatch_once_t>型にする必要があるため、簡単ではありません。

それでは、Swiftのdispatch_onceをあきらめる必要がありますか?コードを一度だけ実行するより安全な方法はありますか?

15
Eric

クロージャーによって初期化された静的プロパティは、遅延して最大1回実行されるため、2回呼び出されても、1回だけ出力されます。

/*
run like:

    Swift once.Swift
    Swift once.Swift run

to see both cases
*/
class Once {
    static let run: Void = {
        print("Behold! \(__FUNCTION__) runs!")
        return ()
    }()
}

if Process.arguments.indexOf("run") != nil {
    let _ = Once.run
    let _ = Once.run
    print("Called twice, but only printed \"Behold\" once, as desired.")
} else {
    print("Note how it's run lazily, so you won't see the \"Behold\" text now.")
}

実行例:

~/W/WhenDoesStaticDefaultRun> Swift once.Swift
Note how it's run lazily, so you won't see the "Behold" text now.
~/W/WhenDoesStaticDefaultRun> Swift once.Swift run
Behold! Once runs!
Called twice, but only printed "Behold" once, as desired.
17

男が医者のところへ行き、「医者、私が足を踏むと痛い」と言いました。医者は「それでやめなさい」と答えました。

意図的にディスパッチトークンを変更した場合は、はい-コードを2回実行できます。しかし、anyの方法で複数の実行を防止するように設計されたロジックを回避すると、それを実行できるようになります。 dispatch_onceは、単純なブール値ではカバーできない初期化と競合条件に関するすべての(非常に)複雑なコーナーケースを処理するため、コードが1回だけ実行されることを保証する最善の方法です。

誰かが誤ってトークンをリセットするのではないかと心配している場合は、トークンをメソッドにラップして、結果が何であるかを明らかにすることができます。次のようなものは、メソッドにトークンのスコープを設定し、深刻な努力なしに誰もそれを変更できないようにします。

func willRunOnce() -> () {
    struct TokenContainer {
        static var token : dispatch_once_t = 0
    }

    dispatch_once(&TokenContainer.token) {
        print("This is printed only on the first call")
    }
}
21
Adam Wright

最善の方法は、必要に応じてリソースを遅延して構築することです。 Swiftはこれを簡単にします。

いくつかのオプションがあります。すでに述べたように、クロージャーを使用して型内の静的プロパティを初期化できます。

ただし、最も簡単なオプションは、グローバル変数(または定数)を定義し、それをクロージャーで初期化してから、初期化コードが1回発生する必要がある場所でその変数を参照することです。

let resourceInit : Void = {
  print("doing once...")
  // do something once
}()

もう1つのオプションは、関数内で型をラップして、呼び出し時に読みやすくすることです。例えば:

func doOnce() {
    struct Resource {
        static var resourceInit : Void = {
            print("doing something once...")
        }()
    }

    let _ = Resource.resourceInit
}

必要に応じて、これを変更できます。たとえば、関数の内部型を使用する代わりに、必要に応じてプライベートグローバル関数と内部関数またはパブリック関数を使用できます。

ただし、最適な方法は、初期化してリソースをグローバルプロパティまたは静的プロパティとして遅延作成するために必要なリソースを決定することだけだと思います。

2
Tom Pelaia