Swift 3、4、5 で dispatch_after GCD を記述するにはどうすればよいですか? 質問する

Swift 3、4、5 で dispatch_after GCD を記述するにはどうすればよいですか? 質問する

dispatch_afterSwift 2 では、 Grand Central Dispatch を使用してアクションを遅延させることができました。

var dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC))) 
dispatch_after(dispatchTime, dispatch_get_main_queue(), { 
    // your function here 
})

しかし、Swift 3 以降ではコンパイルされなくなったようです。最新の Swift でこれを記述する推奨される方法は何ですか?

ベストアンサー1

構文は単純です:

// to run something in 0.1 seconds

DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
    // your code here
}

seconds注記:を として追加する上記の構文は、Double混乱の原因となるようです (特に、nsec を追加することに慣れていたため)。この「秒を として追加するDouble」構文が機能するのは、deadlineが でありDispatchTime、舞台裏で+は を受け取っDoubleて にその秒数を加算する演算子があるためですDispatchTime

public func +(time: DispatchTime, seconds: Double) -> DispatchTime

しかし、本当に に msec、μs、または nsec の整数を追加したい場合は、 a にDispatchTimea を追加することもできます。つまり、次のようにすることができます。DispatchTimeIntervalDispatchTime

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
    // 500 msec, i.e. 0.5 seconds
}

DispatchQueue.main.asyncAfter(deadline: .now() + .microseconds(1_000_000)) {
    // 1m microseconds, i.e. 1 second
}

DispatchQueue.main.asyncAfter(deadline: .now() + .nanoseconds(1_500_000_000)) {
    // 1.5b nanoseconds, i.e. 1.5 seconds
}

+これらはすべて、クラス内の演算子に対する個別のオーバーロード メソッドによりシームレスに動作しますDispatchTime

public func +(time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTime

ディスパッチされたタスクをキャンセルするにはどうすればよいかと質問がありました。これを行うには、 を使用しますDispatchWorkItem。たとえば、これは 5 秒後に実行されるタスクを開始します。または、ビュー コントローラが破棄されて割り当てが解除された場合は、そのdeinitタスクがキャンセルされます。

class ViewController: UIViewController {

    private var item: DispatchWorkItem?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        item = DispatchWorkItem { [weak self] in
            self?.doSomething()
            self?.item = nil
        }
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: item!)
    }

    deinit {
        item?.cancel()
    }
    
    func doSomething() { … }
}

[weak self]でのキャプチャ リストの使用に注意してくださいDispatchWorkItem。これは、強い参照サイクルを回避するために不可欠です。また、これはプリエンプティブ キャンセルを行うのではなく、タスクがまだ開始されていない場合にタスクの開始を停止するだけであることにも注意してください。ただし、呼び出しに遭遇したときにタスクがすでに開始されている場合はcancel()、ブロックの実行が終了します (ブロック内を手動で確認しない限りisCancelled)。


迅速な並行処理

元々の質問は古いGCDdispatch_afterと新しいAPIについてでしたが、新しいSwiftの並行性とその-asyncAfterで同じ動作を実現するにはどうすればよいかという疑問が生じます。iOS 16とmacOS 13の時点では、asyncawaitTask.sleep(for:):

try await Task.sleep(for: .seconds(2))        // 2 seconds

または

try await Task.sleep(for: .milliseconds(200)) // 0.2 seconds

あるいは、iOS 13とmacOS 10.15をサポートする必要がある場合は、Task.sleep(nanoseconds:)その代わり。

キャンセルをサポートするには、以下を保存してくださいTask:

class ViewController: UIViewController {

    private var task: Task<Void, Error>?

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        task = Task {
            try await Task.sleep(for: .seconds(5))
            await doSomething()
        }
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)

        task?.cancel()
    }

    func doSomething() async { … }
}

またはSwiftUIでは、.task {…}Taskビュー修飾子は、「アクションが完了する前にビューが消えた後、ある時点でタスクを自動的にキャンセルします。」後で手動でキャンセルするために保存する必要さえありません。

Swiftの並行性が導入される前は、sleep関数の呼び出しはアンチパターンであり、現在のスレッドをブロックするため、慎重に避けるべきものであったことを認識する必要があります。これはメインスレッドから実行された場合に重大なエラーでしたが、GCDワーカースレッドプールが非常に限られているため、バックグラウンドスレッドで使用した場合も問題がありました。しかし、新しいTask.sleep関数は現在のスレッドをブロックしないため、どのアクター (メイン アクターを含む) からでも安全に使用できます。

おすすめ記事