Làm cách nào để tạo độ trễ trong Swift?


262

Tôi muốn tạm dừng ứng dụng của mình tại một thời điểm nhất định. Nói cách khác, tôi muốn ứng dụng của mình thực thi mã, nhưng sau đó tại một thời điểm nhất định, tạm dừng trong 4 giây và sau đó tiếp tục với phần còn lại của mã. Tôi có thể làm cái này như thế nào?

Tôi đang sử dụng Swift.


Câu trả lời:


271

Thay vì ngủ, sẽ khóa chương trình của bạn nếu được gọi từ luồng UI, hãy xem xét sử dụng NSTimerhoặc hẹn giờ gửi.

Nhưng, nếu bạn thực sự cần một độ trễ trong luồng hiện tại:

do {
    sleep(4)
}

Điều này sử dụng sleepchức năng từ UNIX.


4
Giấc ngủ hoạt động mà không cần nhập darwin, không chắc đây có phải là thay đổi hay không
Dan Beaulieu

17
us ngủ () cũng hoạt động, nếu bạn cần một cái gì đó chính xác hơn độ phân giải 1 giây.
prewett

18
us ngủ () mất một phần triệu giây, do đó, chúng tôi ngủ (1000000) sẽ ngủ trong 1 giây
Harris

40
Cảm ơn vì đã giữ phần mở đầu "không làm điều này".
Dan Rosenstark

2
Tôi sử dụng chế độ ngủ () để mô phỏng các quá trình chạy dài.
William T. Mallard

345

Sử dụng một dispatch_afterkhối trong hầu hết các trường hợp tốt hơn so với sử dụng sleep(time)như luồng mà giấc ngủ được thực hiện bị chặn thực hiện công việc khác. khi sử dụng dispatch_afterluồng được xử lý không bị chặn để nó có thể thực hiện các công việc khác trong lúc này.
Nếu bạn đang làm việc trên luồng chính của ứng dụng, việc sử dụng sleep(time)sẽ không tốt cho trải nghiệm người dùng của ứng dụng của bạn vì UI không phản hồi trong thời gian đó.

Gửi văn bản sau khi lên lịch thực hiện một khối mã thay vì đóng băng chuỗi:

Swift ≥ 3.0

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

Swift <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
Còn những hành động định kỳ thì sao?
qed

1
có khả năng sử dụng gcd cho các tác vụ định kỳ được mô tả trong bài viết này từ tài liệu dành cho nhà phát triển apple nhưng tôi khuyên bạn nên sử dụng NSTimer cho điều đó. câu trả lời này cho thấy làm thế nào để làm điều đó chính xác trong nhanh chóng.
Palle

2
Mã được cập nhật cho Xcode 8.2.1. Trước đây .now() + .seconds(4)cho lỗi:expression type is ambiguous without more context
Richards

Làm cách nào tôi có thể tạm dừng chức năng được gọi từ hàng đợi? @Palle
Mukul Thêm

14
Bạn không còn cần phải thêm .now() + .seconds(5)nó chỉ đơn giản là.now() + 5
cnzac

45

So sánh giữa các cách tiếp cận khác nhau trong swift 3.0

1. Ngủ

Phương pháp này không có cuộc gọi lại. Đặt mã trực tiếp sau khi dòng này được thực hiện trong 4 giây. Nó sẽ ngăn người dùng lặp lại với các thành phần UI như nút kiểm tra cho đến khi hết thời gian. Mặc dù nút này bị đóng băng khi ngủ, nhưng các yếu tố khác như chỉ báo hoạt động vẫn quay mà không bị đóng băng. Bạn không thể kích hoạt hành động này một lần nữa trong khi ngủ.

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

nhập mô tả hình ảnh ở đây

2. Công văn, thực hiện và hẹn giờ

Ba phương thức này hoạt động tương tự nhau, tất cả chúng đều chạy trên luồng nền với các cuộc gọi lại, chỉ với cú pháp khác nhau và các tính năng hơi khác nhau.

Công văn thường được sử dụng để chạy một cái gì đó trên luồng nền. Nó có hàm gọi lại như là một phần của chức năng gọi

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

Thực hiện là một bộ đếm thời gian đơn giản hóa. Nó thiết lập một bộ đếm thời gian với độ trễ, và sau đó kích hoạt chức năng bằng bộ chọn.

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

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

Và cuối cùng, bộ đếm thời gian cũng cung cấp khả năng lặp lại cuộc gọi lại, điều này không hữu ích trong trường hợp này

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


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

Đối với cả ba phương pháp này, khi bạn nhấp vào nút để kích hoạt chúng, UI sẽ không bị đóng băng và bạn được phép nhấp lại vào nó. Nếu bạn bấm vào nút một lần nữa, một bộ đếm thời gian khác được thiết lập và cuộc gọi lại sẽ được kích hoạt hai lần.

nhập mô tả hình ảnh ở đây

Tóm lại là

Không ai trong số bốn phương pháp hoạt động đủ tốt chỉ bằng chính họ. sleepsẽ vô hiệu hóa tương tác người dùng, do đó màn hình " đóng băng " (không thực sự) và dẫn đến trải nghiệm người dùng xấu. Ba phương pháp khác sẽ không đóng băng màn hình, nhưng bạn có thể kích hoạt chúng nhiều lần và hầu hết các lần, bạn muốn đợi cho đến khi bạn nhận lại cuộc gọi trước khi cho phép người dùng thực hiện lại cuộc gọi.

Vì vậy, một thiết kế tốt hơn sẽ được sử dụng một trong ba phương pháp không đồng bộ với chặn màn hình. Khi người dùng nhấp vào nút, che toàn bộ màn hình bằng một số chế độ xem mờ với chỉ báo hoạt động xoay tròn ở trên, cho người dùng biết rằng việc nhấp vào nút đang được xử lý. Sau đó xóa chế độ xem và chỉ báo trong chức năng gọi lại, thông báo cho người dùng rằng hành động được xử lý đúng cách, v.v.


1
Lưu ý rằng trong Swift 4, khi tắt suy luận objc, bạn sẽ cần thêm @objcvào trước chức năng gọi lại cho perform(...)tùy chọn. Giống như vậy:@objc func callback() {
leanne

32

Tôi đồng ý với Palle rằng sử dụng dispatch_aftermột lựa chọn tốt ở đây. Nhưng có lẽ bạn không thích các cuộc gọi GCD vì chúng khá khó chịu khi viết . Thay vào đó, bạn có thể thêm người trợ giúp tiện dụng này :

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)
        }
    }
}

Bây giờ bạn chỉ cần trì hoãn mã của mình trên một luồng nền như thế này:

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

Việc trì hoãn mã trên luồng chính thậm chí còn đơn giản hơn:

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

Nếu bạn thích một Framework cũng có một số tính năng tiện dụng hơn thì hãy kiểm tra HandySwift . Bạn có thể thêm nó vào dự án của bạn thông qua Carthage hoặc Accio sau đó sử dụng chính xác như trong các ví dụ trên:

import HandySwift    

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

Điều này có vẻ rất hữu ích. Bạn có nhớ cập nhật nó với phiên bản 3.0 nhanh chóng. Cảm ơn
grahamcracker1234

Tôi đã bắt đầu chuyển HandySwift sang Swift 3 và dự định phát hành phiên bản mới vào tuần tới. Sau đó, tôi cũng sẽ cập nhật kịch bản ở trên. Giữ nguyên.
Jeehut

Quá trình di chuyển của HandySwift sang Swift 3 hiện đã hoàn tất và được phát hành trong phiên bản 1.3.0. Tôi cũng vừa cập nhật mã ở trên để hoạt động với Swift 3! :)
Jeehut

1
Tại sao bạn nhân với NSEC_PER_SEC sau đó chia lại cho nó? Điều đó có thừa không? (ví dụ: Double (Int64 (giây * Double (NSEC_PER_SEC))) / Double (NSEC_PER_SEC)) Bạn không thể chỉ viết 'DispatchTime.now () + Double (giây)?
Đánh dấu A. Donohoe

1
Cảm ơn bạn @MarqueIV, bạn đã đúng. Tôi vừa cập nhật mã. Nó chỉ đơn giản là kết quả của việc chuyển đổi tự động từ Xcode 8 và chuyển đổi loại là cần thiết trước đây vì DispatchTimekhông có sẵn trong Swift 2. Đó là let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))trước đây.
Jeehut

24

Bạn cũng có thể làm điều này với Swift 3.

Thực hiện chức năng sau khi trì hoãn như vậy.

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

Trong Swift 4.2 và Xcode 10.1

Bạn có tổng cộng 4 cách để trì hoãn. Trong số các tùy chọn này, tốt hơn là gọi hoặc thực hiện một chức năng sau một thời gian. Các giấc ngủ () là trường hợp ít nhất là trong sử dụng.

Lựa chọn 1.

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

}

Lựa chọn 2.

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

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

Lựa chọn 3.

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

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

}

Lựa chọn 4.

sleep(5)

Nếu bạn muốn gọi một chức năng sau một thời gian để thực thi một cái gì đó đừng sử dụng ngủ .


19

NST

Câu trả lời của @nneonneo đã đề xuất sử dụng NSTimernhưng không chỉ ra cách thực hiện. Đây là cú pháp cơ bản:

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

Đây là một dự án rất đơn giản để cho thấy nó có thể được sử dụng như thế nào. Khi nhấn nút, nó sẽ khởi động bộ hẹn giờ sẽ gọi một chức năng sau khi trễ nửa giây.

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")
    }
}

Sử dụng dispatch_time(như trong câu trả lời của Palle ) là một tùy chọn hợp lệ khác. Tuy nhiên, thật khó để hủy bỏ . Với NSTimer, để hủy một sự kiện bị trì hoãn trước khi nó xảy ra, tất cả những gì bạn cần làm là gọi

timer.invalidate()

Việc sử dụng sleepkhông được khuyến khích, đặc biệt là trên luồng chính, vì nó dừng tất cả các công việc đang được thực hiện trên luồng.

Xem ở đây để trả lời đầy đủ hơn của tôi.


7

Hãy thử triển khai sau trong Swift 3.0

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

Sử dụng

delayWithSeconds(1) {
   //Do something
}

7

Bạn có thể tạo tiện ích mở rộng để sử dụng chức năng trì hoãn dễ dàng (Cú pháp: 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)
    }
}

Cách sử dụng trong UIViewControll

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

6

Nếu bạn cần đặt độ trễ nhỏ hơn một giây, không cần thiết phải đặt tham số .seconds. Tôi hy vọng điều này hữu ích cho ai đó.

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
Làm DispatchQueue.main.asyncAfter(deadline: .now() + 4) {/*Do stuff*/}có lẽ đúng hơn
eonist

5

Nếu mã của bạn đang chạy trong một luồng nền, hãy tạm dừng luồng bằng phương thức này trong Foundation :Thread.sleep(forTimeInterval:)

Ví dụ:

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

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

(Xem các câu trả lời khác cho các đề xuất khi mã của bạn đang chạy trên luồng chính.)


3

Để tạo độ trễ thời gian đơn giản, bạn có thể nhập Darwin và sau đó sử dụng chế độ ngủ (giây) để thực hiện độ trễ. Tuy nhiên, điều đó chỉ mất toàn bộ giây, do đó, để có các phép đo chính xác hơn, bạn có thể nhập Darwin và sử dụng thời gian ngủ (một phần triệu giây) để đo rất chính xác. Để kiểm tra điều này, tôi đã viết:

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

Mà in, sau đó đợi 1 giây và in, sau đó đợi 0,4 giây rồi in. Tất cả đều hoạt động như mong đợi.


-3

đây là cách đơn giản nhất

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

2
Đây không phải là một phần của Foundation, như tôi nghĩ, được bao gồm trong 'CocoSwift' Cocoapod. Bạn nên làm rõ điều đó trong câu trả lời của bạn.
Jan Nash

Liệu swift có một cái gì đó chờ đợi một cái gì đó xảy ra hay không?
Saeed Rahmatolahi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.