ฉันจะเขียน dispatch_after GCD ใน Swift 3, 4 และ 5 ได้อย่างไร


445

ใน Swift 2 ฉันสามารถใช้dispatch_afterเพื่อหน่วงเวลาการดำเนินการโดยใช้การส่งศูนย์กลางใหญ่:

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 ยุคใหม่คืออะไร?


6
ข้อมูลเพิ่มเติมเกี่ยวกับกระบวนการย้ายถิ่นสามารถดูได้ที่นี่: https://swift.org/migration-guide/ส่วน "Dispatch" เกี่ยวข้องกับคำถามนี้
tonik12

คำถามของคุณควรเป็นUInt64อย่างไร
ฮันนี่

คำตอบ:


1125

ไวยากรณ์เป็นเพียง:

// to run something in 0.1 seconds

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

หมายเหตุไวยากรณ์ข้างต้นของการเพิ่มsecondsเป็นDoubleน่าจะเป็นแหล่งที่มาของความสับสน (ESP ตั้งแต่เราได้คุ้นเคยกับการเพิ่ม nsec บริการ) "เพิ่มวินาทีในขณะที่Double" ไวยากรณ์ใช้งานได้เนื่องจากdeadlineเป็นDispatchTimeและหลังฉากมี+โอเปอเรเตอร์ที่จะใช้เวลาDoubleและเพิ่มจำนวนวินาทีนั้นในDispatchTime:

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

แต่ถ้าคุณอยากจะเพิ่มเต็มจำนวนมิลลิวินาที, ไมโครวินาทีหรือ nsec ไปDispatchTimeคุณยังสามารถเพิ่มไปยังDispatchTimeInterval DispatchTimeนั่นหมายความว่าคุณสามารถทำได้:

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
    os_log("500 msec seconds later")
}

DispatchQueue.main.asyncAfter(deadline: .now() + .microseconds(1_000_000)) {
    os_log("1m μs seconds later")
}

DispatchQueue.main.asyncAfter(deadline: .now() + .nanoseconds(1_500_000_000)) {
    os_log("1.5b nsec seconds later")
}

สิ่งเหล่านี้ทำงานได้อย่างราบรื่นเนื่องจากวิธีการโอเวอร์โหลดแยกนี้สำหรับ+ผู้ปฏิบัติงานในDispatchTimeชั้นเรียน

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

มันถูกถามว่าจะไปเกี่ยวกับการยกเลิกงานที่จัดส่ง DispatchWorkItemการทำเช่นนี้การใช้งาน ตัวอย่างเช่นสิ่งนี้จะเริ่มงานที่จะดำเนินการภายในห้าวินาทีหรือหากตัวควบคุมมุมมองถูกยกเลิกและยกเลิกการจัดสรรคืนงาน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ภายในบล็อก)


5
ขอบคุณสำหรับการชี้ให้เห็นและในความเป็นจริงswift.org/migration-guideกล่าวถึงความจำเป็นในการเปลี่ยนแปลงด้วยมือ
matt

1
โอ้ขอโทษ. มันสายเกินไปแล้วที่นี่ :) คิดว่าระเบียบทั้งหมดควรไปจริง แต่ไม่ได้ใช้การกระโดด IMO โซลูชัน "ที่เรียบง่าย" เป็นโซลูชันที่แก้ปัญหาเดียว
tobiasdm

1
@Rob ฉันจะยกเลิกมันได้อย่างไร? ขอบคุณ
kemicofa ghost

ตกลงคุณจะเพิ่มการรอแบบไดนามิกได้อย่างไร ตัวอย่างเช่นฉันมีหมายเลขให้: Float = 1.0 และ. ตอนนี้ () +. มิลลิวินาที (จำนวน) ไม่ทำงาน สองเท่า (ตัวเลข) ฉันคิดไม่ออก
Kjell

2
DispatchTimeIntervalซ้ำเช่นต้อง.milliseconds Intแต่ถ้าเพียงการเพิ่มวินาทีฉันต้องการใช้เช่นDouble let n: Double = 1.0; queue.asyncAfter(deadline: .now() + n) { ... }
Rob

128

สวิฟท์ 4:

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
   // Code
}

สำหรับเวลา.seconds(Int), .microseconds(Int)และ.nanoseconds(Int)นอกจากนี้ยังอาจถูกนำมาใช้


7
.millisecondsดีกว่า Double
DawnSong

5
ดีมาก. หมายเหตุสำหรับผู้อื่น: คุณสามารถใช้DispatchTimeIntervalค่า enum อื่น ๆ ได้เช่นกัน case seconds(Int) case milliseconds(Int) case microseconds(Int) case nanoseconds(Int)
Rob MacEachern

@ RobMacEachern ขอบคุณที่เป็นคำแนะนำที่ดีฉันเพิ่มไปยังคำตอบ
Sverrisson

2
.milliseconds is better than Double. - ฉันต้องการสิ่งนั้นบนเสื้อยืด;)
คริสเจ้าชาย

58

หากคุณต้องการฟังก์ชั่นการหน่วงเวลา

สวิฟต์ 4 และ 5

func delay(interval: TimeInterval, closure: @escaping () -> Void) {
     DispatchQueue.main.asyncAfter(deadline: .now() + interval) {
          closure()
     }
}

คุณสามารถใช้มันเหมือน:

delay(interval: 1) { 
    print("Hi!")
}

DispatchQueue.main.asyncAfter (กำหนดเวลา:) ไม่ทำงาน มันบอกว่ามันจะไม่โอเวอร์โหลดเมธอดใด ๆ จากซูเปอร์คลาสของมัน
Fabrizio Bartolomucci

7
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: closure)ง่ายกว่า
DawnSong

16

หลังการเปิดตัว Swift 3 ต้องเพิ่ม @escaping ด้วย

func delay(_ delay: Double, closure: @escaping () -> ()) {
  DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
    closure()
  }
}

5

รสชาติที่แตกต่างของคำตอบที่ได้รับการยอมรับ

สวิฟต์ 4

DispatchQueue.main.asyncAfter(deadline: .now() + 0.1 + .milliseconds(500) + 
.microseconds(500) + .nanoseconds(1000)) {
                print("Delayed by 0.1 second + 500 milliseconds + 500 microseconds + 
                      1000 nanoseconds)")
 }

5

สวิฟต์ 4

คุณสามารถสร้างส่วนขยายบน DispatchQueue และเพิ่มการหน่วงเวลาของฟังก์ชันซึ่งใช้DispatchQueueฟังก์ชัน asyncAfter ภายใน

extension DispatchQueue {
   static func delay(_ delay: DispatchTimeInterval, closure: @escaping () -> ()) {
      DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: closure)
   }
}

และการใช้งาน

DispatchQueue.delay(.milliseconds(10)) {
   print("task to be done")
}

2
สิ่งนี้แตกต่างจากคำตอบของ @ rockdaswift อย่างไร
brandonscript

ดังที่ฉันกล่าวถึงมันจะตัด asyncAfter ข้างในฟังก์ชั่น performAfter ซึ่งใช้เวลาเป็นพารามิเตอร์และสามารถโทรหาได้ง่ายขึ้นโดยใช้แค่ performAfter (หน่วงเวลา: 2) {}
Suhit Patil

พารามิเตอร์การปิดจะไม่หนีออกมาโดยค่าเริ่มต้น @escaping บ่งชี้ว่าพารามิเตอร์การปิดอาจหนีได้ เพิ่มพารามิเตอร์ @ escaping ในการปิดเพื่อบันทึกความผิดพลาดที่อาจเกิดขึ้น
Suhit Patil

3

โทร DispatchQueue.main.after(when: DispatchTime, execute: () -> Void)

ฉันขอแนะนำให้ใช้เครื่องมือ Xcode เพื่อแปลงเป็น Swift 3 (แก้ไข> แปลง> เป็นไวยากรณ์ของ Swift ปัจจุบัน) มันจับสิ่งนี้สำหรับฉัน


3

ใน Swift 4.1 และ Xcode 9.4.1

คำตอบง่ายๆคือ ...

//To call function after 5 seconds time
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
//Here call your function
}

3
ไม่แน่ใจว่านี่แตกต่างจากคำตอบที่ยอมรับหรือไม่
brandonscript


1

ไม่มีคำตอบที่กล่าวถึงที่ทำงานอยู่บนเธรดที่ไม่ใช่ main ดังนั้นเพิ่ม 2 cents ของฉัน

บนคิวหลัก (เธรดหลัก)

let mainQueue = DispatchQueue.main
let deadline = DispatchTime.now() + .seconds(10)
mainQueue.asyncAfter(deadline: deadline) {
    // ...
}

หรือ

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(10)) { 
    // ...
}

บนคิวโกลบอล (ไม่ใช่เธรดหลักอ้างอิงตาม QOS ที่ระบุ)

let backgroundQueue = DispatchQueue.global()
let deadline = DispatchTime.now() + .milliseconds(100)
backgroundQueue.asyncAfter(deadline: deadline, qos: .background) { 
    // ...
}

หรือ

DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + .milliseconds(100), qos: .background) {
    // ...
}

0

สิ่งนี้ใช้ได้กับฉันใน Swift 3

let time1 = 8.23
let time2 = 3.42

// Delay 2 seconds


DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
    print("Sum of times: \(time1 + time2)")
}

5
ไม่แน่ใจว่าสิ่งนี้แตกต่างจากคำตอบที่ยอมรับหรือไม่
brandonscript


0

ลองนี้

let when = DispatchTime.now() + 1.5
    DispatchQueue.main.asyncAfter(deadline: when) {
        //some code
    }

ไม่แน่ใจว่าสิ่งนี้แตกต่างจากคำตอบที่ได้รับผลกระทบอย่างไร
brandonscript
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.