dispatch_after
Swift 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 にDispatchTime
a を追加することもできます。つまり、次のようにすることができます。DispatchTimeInterval
DispatchTime
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の時点では、async
await
Task.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
関数は現在のスレッドをブロックしないため、どのアクター (メイン アクターを含む) からでも安全に使用できます。