จะสร้างความล่าช้าใน Swift ได้อย่างไร?


262

ฉันต้องการหยุดแอพชั่วคราวที่จุดใดจุดหนึ่ง กล่าวอีกนัยหนึ่งฉันต้องการให้แอปของฉันเรียกใช้รหัส แต่เมื่อถึงจุดหนึ่งหยุดชั่วคราวเป็นเวลา 4 วินาทีแล้วดำเนินการต่อโดยใช้รหัสที่เหลือ ฉันจะทำสิ่งนี้ได้อย่างไร

ฉันใช้สวิฟท์


คำตอบ:


270

แทนที่จะเป็นโหมดสลีปซึ่งจะล็อกโปรแกรมของคุณหากถูกเรียกจากเธรด UI ลองใช้NSTimerหรือตั้งเวลาส่ง

แต่ถ้าคุณต้องการความล่าช้าในเธรดปัจจุบัน:

do {
    sleep(4)
}

สิ่งนี้ใช้sleepฟังก์ชันจาก UNIX


4
การนอนหลับทำงานได้โดยไม่ต้องนำเข้าดาร์วินไม่แน่ใจว่านี่เป็นการเปลี่ยนแปลงหรือเปล่า
Dan Beaulieu

17
usleep () ใช้งานได้เช่นกันหากคุณต้องการบางสิ่งที่แม่นยำยิ่งขึ้นด้วยความละเอียด 1 วินาที
prewett

18
usleep () ใช้เวลาหนึ่งในล้านวินาทีดังนั้น usleep (1000000) จะนอน 1 วินาที
Harris

40
ขอบคุณสำหรับการรักษาคำนำหน้า "อย่าทำแบบนี้" สั้น ๆ
Dan Rosenstark

2
ฉันใช้ sleep () เพื่อจำลองกระบวนการพื้นหลังที่ใช้เวลานาน
William T. Mallard

345

การใช้dispatch_afterบล็อกในกรณีส่วนใหญ่ดีกว่าการใช้sleep(time)เนื่องจากเธรดที่ดำเนินการสลีปนั้นถูกบล็อกไม่ให้ทำงานอื่น เมื่อใช้dispatch_afterเธรดที่ทำงานอยู่จะไม่ถูกบล็อกดังนั้นจึงสามารถทำงานอื่นได้ในระหว่างนี้
หากคุณกำลังทำงานกับเธรดหลักของแอปพลิเคชันของคุณการใช้sleep(time)ไม่ดีสำหรับประสบการณ์การใช้งานแอพของคุณเนื่องจาก UI ไม่ตอบสนองในช่วงเวลานั้น

ส่งหลังจากกำหนดเวลาการดำเนินการของบล็อกของรหัสแทนที่จะแช่แข็งเธรด:

Swift ≥ 3.0

let seconds = 4.0
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
    // Put your code which should be executed with a delay here
}

สวิฟท์ <3.0

let time = dispatch_time(dispatch_time_t(DISPATCH_TIME_NOW), 4 * Int64(NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue()) {
    // Put your code which should be executed with a delay here
}

1
สิ่งที่เกี่ยวกับการกระทำเป็นระยะ?
QED

1
มีความเป็นไปได้ที่จะใช้ gcd สำหรับงานตามกำหนดเวลาซึ่งอธิบายไว้ในบทความนี้จากเอกสารประกอบสำหรับนักพัฒนา appleแต่ฉันอยากจะแนะนำให้ใช้ NSTimer คำตอบนี้แสดงวิธีการทำสิ่งนั้นอย่างรวดเร็ว
Palle

2
รหัสอัพเดทสำหรับ Xcode 8.2.1 ก่อนหน้านี้.now() + .seconds(4)แสดงข้อผิดพลาด:expression type is ambiguous without more context
Richards

ฉันจะระงับฟังก์ชันที่เรียกใช้จากคิวได้อย่างไร @Palle
Mukul More

14
คุณไม่จำเป็นต้องเพิ่มอีกต่อไป.now() + .seconds(5)ง่ายๆ.now() + 5
cnzac

45

เปรียบเทียบระหว่างวิธีการต่าง ๆ ใน swift 3.0

1. นอนหลับ

วิธีนี้ไม่มีการโทรกลับ ใส่รหัสโดยตรงหลังจากบรรทัดนี้จะถูกดำเนินการใน 4 วินาที มันจะหยุดผู้ใช้ไม่ให้ทำการวนซ้ำด้วยองค์ประกอบ UI เช่นปุ่มทดสอบจนกว่าจะหมดเวลา แม้ว่าปุ่มจะเป็นชนิดของการแช่แข็งเมื่อนอนหลับในองค์ประกอบอื่น ๆ เช่นตัวบ่งชี้กิจกรรมยังคงหมุนโดยไม่ต้องแช่แข็ง คุณไม่สามารถเรียกการกระทำนี้อีกครั้งในระหว่างที่หลับ

sleep(4)
print("done")//Do stuff here

ป้อนคำอธิบายรูปภาพที่นี่

2. จัดส่งดำเนินการและจับเวลา

วิธีการทั้งสามนี้ทำงานคล้ายกันพวกเขาทั้งหมดกำลังทำงานบนเธรดพื้นหลังด้วยการโทรกลับเพียงแค่มีไวยากรณ์ที่แตกต่างกันและคุณลักษณะที่แตกต่างกันเล็กน้อย

การกระจายมักใช้เพื่อเรียกใช้บางสิ่งบางอย่างบนเธรดพื้นหลัง มันมีการโทรกลับเป็นส่วนหนึ่งของการเรียกใช้ฟังก์ชั่น

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4), execute: {
    print("done")
})

การแสดงเป็นตัวจับเวลาแบบง่าย มันตั้งเวลาด้วยความล่าช้าและจากนั้นเรียกใช้ฟังก์ชั่นโดยตัวเลือก

perform(#selector(callback), with: nil, afterDelay: 4.0)

func callback() {
    print("done")
}}

และในที่สุดตัวจับเวลายังให้ความสามารถในการโทรกลับซ้ำซึ่งไม่เป็นประโยชน์ในกรณีนี้

Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(callback), userInfo: nil, repeats: false)


func callback() {
    print("done")
}}

สำหรับวิธีการทั้งสามนี้เมื่อคุณคลิกที่ปุ่มเพื่อเรียกใช้ UI จะไม่หยุดและคุณได้รับอนุญาตให้คลิกอีกครั้ง หากคุณคลิกที่ปุ่มอีกครั้งตัวจับเวลาอื่นจะถูกตั้งค่าและการโทรกลับจะถูกเรียกสองครั้ง

ป้อนคำอธิบายรูปภาพที่นี่

สรุปแล้ว

ไม่มีวิธีใดในสี่ที่ทำงานได้ดีพอเพียงด้วยตัวเอง sleepจะปิดการใช้งานการโต้ตอบของผู้ใช้ดังนั้นหน้าจอ " ค้าง " (ไม่จริง) และส่งผลให้ประสบการณ์การใช้งานที่ไม่ดี อีกสามวิธีจะไม่หยุดหน้าจอ แต่คุณสามารถเรียกใช้หลายครั้งและส่วนใหญ่คุณต้องการรอจนกว่าคุณจะได้รับการโทรกลับก่อนที่จะอนุญาตให้ผู้ใช้โทรออกอีกครั้ง

ดังนั้นการออกแบบที่ดีกว่าจะใช้หนึ่งในสามวิธีการซิงค์กับการบล็อกหน้าจอ เมื่อผู้ใช้คลิกที่ปุ่มให้ครอบคลุมทั้งหน้าจอด้วยมุมมองโปร่งแสงพร้อมตัวบ่งชี้กิจกรรมการหมุนด้านบนบอกผู้ใช้ว่าการคลิกปุ่มนั้นกำลังถูกจัดการ จากนั้นลบมุมมองและตัวบ่งชี้ในฟังก์ชั่นโทรกลับบอกผู้ใช้ว่าการดำเนินการได้รับการจัดการอย่างเหมาะสม ฯลฯ


1
โปรดทราบว่าใน Swift 4 เมื่อปิดการอนุมาน objc คุณจะต้องเพิ่ม@objcฟังก์ชัน callback ด้านหน้าสำหรับperform(...)ตัวเลือก ชอบโดย:@objc func callback() {
ลีแอนน์

32

ฉันเห็นด้วยกับ Palle ว่าการใช้dispatch_afterเป็นตัวเลือกที่ดีที่นี่ แต่คุณอาจไม่ชอบโทร GCD ที่พวกเขาจะค่อนข้างน่ารำคาญที่จะเขียน คุณสามารถเพิ่มผู้ช่วยที่มีประโยชน์นี้แทน:

public func delay(bySeconds seconds: Double, dispatchLevel: DispatchLevel = .main, closure: @escaping () -> Void) {
    let dispatchTime = DispatchTime.now() + seconds
    dispatchLevel.dispatchQueue.asyncAfter(deadline: dispatchTime, execute: closure)
}

public enum DispatchLevel {
    case main, userInteractive, userInitiated, utility, background
    var dispatchQueue: DispatchQueue {
        switch self {
        case .main:                 return DispatchQueue.main
        case .userInteractive:      return DispatchQueue.global(qos: .userInteractive)
        case .userInitiated:        return DispatchQueue.global(qos: .userInitiated)
        case .utility:              return DispatchQueue.global(qos: .utility)
        case .background:           return DispatchQueue.global(qos: .background)
        }
    }
}

ตอนนี้คุณเพียงแค่ชะลอโค้ดของคุณบนเธรดพื้นหลังดังนี้:

delay(bySeconds: 1.5, dispatchLevel: .background) { 
    // delayed code that will run on background thread
}

การหน่วงเวลาโค้ดบนเธรดหลักนั้นทำได้ง่ายกว่า:

delay(bySeconds: 1.5) { 
    // delayed code, by default run in main thread
}

ถ้าคุณชอบกรอบที่ยังมีคุณสมบัติที่มีประโยชน์มากขึ้นบางแล้วเช็คเอาท์HandySwift คุณสามารถเพิ่มลงในโครงการของคุณผ่านทางCarthageหรือAccioจากนั้นใช้เหมือนในตัวอย่างด้านบน:

import HandySwift    

delay(by: .seconds(1.5)) { 
    // delayed code
}

สิ่งนี้ดูมีประโยชน์มาก คุณสนใจที่จะอัพเดทด้วยเวอร์ชั่น 3.0 หรือไม่ ขอบคุณ
grahamcracker1234

ฉันเริ่มย้าย HandySwift ไปยัง Swift 3 และวางแผนที่จะวางจำหน่ายเวอร์ชันใหม่ในสัปดาห์หน้า จากนั้นฉันจะอัปเดตสคริปต์ด้านบนด้วย คอยติดตาม.
Jeehut

การย้าย HandySwift ไปเป็น Swift 3 เสร็จสมบูรณ์แล้วและวางจำหน่ายในเวอร์ชั่น 1.3.0 ฉันเพิ่งอัปเดตโค้ดด้านบนเพื่อทำงานกับ Swift 3! :)
Jeehut

1
ทำไมคุณคูณด้วย NSEC_PER_SEC แล้วหารด้วยอีกครั้ง ไม่ซ้ำซ้อนที่? (เช่น Double (Int64 (วินาที * Double (NSEC_PER_SEC))) / Double (NSEC_PER_SEC)) คุณไม่สามารถเขียน 'DispatchTime.now () + Double (วินาที) ได้ไหม
Mark A. Donohoe

1
ขอบคุณ @MarqueIV คุณพูดถูก ฉันเพิ่งปรับปรุงรหัส มันเป็นเพียงผลลัพธ์ของการแปลงอัตโนมัติจาก Xcode 8 และต้องการการแปลงประเภทมาก่อนเนื่องจากDispatchTimeไม่สามารถใช้งานได้ใน Swift 2 เมื่อlet dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))ก่อน
Jeehut

24

คุณสามารถทำได้ด้วย Swift 3

ดำเนินการฟังก์ชั่นหลังจากล่าช้าเช่นนั้น

override func viewDidLoad() {
    super.viewDidLoad()

    self.perform(#selector(ClassName.performAction), with: nil, afterDelay: 2.0)
}


     @objc func performAction() {
//This function will perform after 2 seconds
            print("Delayed")
        }

23

ใน Swift 4.2 และ Xcode 10.1

คุณมีหน่วงเวลาทั้งหมด 4 วิธี ออกจากตัวเลือกเหล่านี้1จะดีกว่าในการโทรหรือรันฟังก์ชั่นหลังจากเวลา การนอนหลับ ()เป็นกรณีอย่างน้อยในการใช้งาน

ตัวเลือกที่ 1.

DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
    self.yourFuncHere()
}
//Your function here    
func yourFuncHere() {

}

ตัวเลือก 2

perform(#selector(yourFuncHere2), with: nil, afterDelay: 5.0)

//Your function here  
@objc func yourFuncHere2() {
    print("this is...")
}

ตัวเลือก 3

Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(yourFuncHere3), userInfo: nil, repeats: false)

//Your function here  
@objc func yourFuncHere3() {

}

ตัวเลือก 4

sleep(5)

หากคุณต้องการเรียกใช้ฟังก์ชั่นหลังจากผ่านไประยะหนึ่งเพื่อดำเนินการบางอย่างอย่าใช้โหมดสลีป


19

NSTimer

คำตอบโดย @nneonneo แนะนำให้ใช้NSTimerแต่ไม่ได้แสดงว่าต้องทำอย่างไร นี่คือไวยากรณ์พื้นฐาน:

let delay = 0.5 // time in seconds
NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: #selector(myFunctionName), userInfo: nil, repeats: false)

นี่เป็นโครงการที่ง่ายมากที่จะแสดงว่ามันอาจถูกใช้อย่างไร เมื่อกดปุ่มมันจะเริ่มจับเวลาที่จะเรียกใช้ฟังก์ชั่นหลังจากความล่าช้าครึ่งวินาที

import UIKit
class ViewController: UIViewController {

    var timer = NSTimer()
    let delay = 0.5
    
    // start timer when button is tapped
    @IBAction func startTimerButtonTapped(sender: UIButton) {

        // cancel the timer in case the button is tapped multiple times
        timer.invalidate()

        // start the timer
        timer = NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false)
    }

    // function to be called after the delay
    func delayedAction() {
        print("action has started")
    }
}

การใช้dispatch_time(ตามคำตอบของ Palle ) เป็นอีกตัวเลือกที่ใช้ได้ แต่ก็เป็นเรื่องยากที่จะยกเลิก ด้วยNSTimerการยกเลิกเหตุการณ์ล่าช้าก่อนที่จะเกิดขึ้นสิ่งที่คุณต้องทำคือโทรออก

timer.invalidate()

ใช้sleepไม่แนะนำโดยเฉพาะอย่างยิ่งในหัวข้อหลักเพราะมันจะหยุดการทำงานทั้งหมดจะถูกดำเนินการในหัวข้อ

ดูที่นี่สำหรับคำตอบแบบเต็มของฉัน


7

ลองใช้งานต่อไปนี้ใน Swift 3.0

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

การใช้

delayWithSeconds(1) {
   //Do something
}

7

คุณสามารถสร้างส่วนขยายเพื่อใช้ฟังก์ชั่นการหน่วงเวลาได้อย่างง่ายดาย (ไวยากรณ์: Swift 4.2+)

extension UIViewController {
    func delay(_ delay:Double, closure:@escaping ()->()) {
        DispatchQueue.main.asyncAfter(
            deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
    }
}

วิธีใช้ใน UIViewController

self.delay(0.1, closure: {
   //execute code
})

6

หากคุณต้องการตั้งค่าการหน่วงเวลาน้อยกว่าหนึ่งวินาทีคุณไม่จำเป็นต้องตั้งค่าพารามิเตอร์. วินาที ฉันหวังว่านี่จะเป็นประโยชน์กับใครบางคน

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
        // your code hear
})

5
DispatchQueue.global(qos: .background).async {
    sleep(4)
    print("Active after 4 sec, and doesn't block main")
    DispatchQueue.main.async{
        //do stuff in the main thread here
    }
}

4
การทำDispatchQueue.main.asyncAfter(deadline: .now() + 4) {/*Do stuff*/}น่าจะถูกต้องมากขึ้น✌️
eonist

5

หากรหัสของคุณกำลังทำงานอยู่ในเธรดพื้นหลังหยุดเธรดชั่วคราวโดยใช้วิธีนี้ในมูลนิธิ :Thread.sleep(forTimeInterval:)

ตัวอย่างเช่น:

DispatchQueue.global(qos: .userInitiated).async {

    // Code is running in a background thread already so it is safe to sleep
    Thread.sleep(forTimeInterval: 4.0)
}

(ดูคำตอบอื่น ๆ สำหรับคำแนะนำเมื่อโค้ดของคุณทำงานบนเธรดหลัก)


3

หากต้องการสร้างการหน่วงเวลาอย่างง่ายคุณสามารถนำเข้าดาร์วินจากนั้นใช้โหมดสลีป (วินาที) เพื่อหน่วงเวลา แม้ว่าจะใช้เวลาเพียงไม่กี่วินาทีเท่านั้นดังนั้นสำหรับการวัดที่แม่นยำยิ่งขึ้นคุณสามารถนำเข้าดาร์วินและใช้ usleep (หนึ่งในล้านของวินาที) เพื่อการวัดที่แม่นยำมาก เพื่อทดสอบสิ่งนี้ฉันเขียนว่า:

import Darwin
print("This is one.")
sleep(1)
print("This is two.")
usleep(400000)
print("This is three.")

สิ่งที่พิมพ์แล้วรอ 1 วินาทีและพิมพ์แล้วรอ 0.4 วินาทีแล้วพิมพ์ ทำงานได้ตามที่คาดไว้


-3

นี่คือวิธีที่ง่ายที่สุด

    delay(0.3, closure: {
        // put her any code you want to fire it with delay
        button.removeFromSuperview()   
    })

2
นี่ไม่ใช่ส่วนหนึ่งของมูลนิธิ แต่อย่างที่ฉันถือว่ารวมอยู่ใน 'HandySwift' Cocoapod คุณควรชี้แจงว่าในคำตอบของคุณ
Jan Nash

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