Bagaimana cara membuat penundaan di Swift?

262

Saya ingin menjeda aplikasi saya pada titik tertentu. Dengan kata lain, saya ingin aplikasi saya mengeksekusi kode, tetapi kemudian pada titik tertentu, berhenti selama 4 detik, dan kemudian melanjutkan dengan sisa kode. Bagaimana saya bisa melakukan ini?

Saya menggunakan Swift.

Schuey999
sumber

Jawaban:

271

Alih-alih tidur, yang akan mengunci program Anda jika dipanggil dari utas UI, pertimbangkan untuk menggunakan NSTimeratau pengatur waktu pengiriman.

Tapi, jika Anda benar-benar membutuhkan penundaan di utas saat ini:

do {
    sleep(4)
}

Ini menggunakan sleepfungsi dari UNIX.

nneonneo
sumber
4
sleep berfungsi tanpa mengimpor darwin, tidak yakin apakah ini perubahan
Dan Beaulieu
17
usleep () berfungsi juga, jika Anda membutuhkan sesuatu yang lebih tepat yaitu resolusi 1 detik.
prewett
18
usleep () membutuhkan sepersejuta detik, jadi usleep (1000000) akan tidur selama 1 detik
Harris
40
Terima kasih telah membuat basa-basi "jangan lakukan ini" singkat.
Dan Rosenstark
2
Saya menggunakan sleep () untuk mensimulasikan proses latar belakang yang sudah berjalan lama.
William T. Mallard
345

Menggunakan dispatch_afterblok dalam banyak kasus lebih baik daripada menggunakan sleep(time)sebagai benang yang tidur dilakukan diblokir dari melakukan pekerjaan lain. saat menggunakan dispatch_afterutas yang sedang dikerjakan tidak diblokir sehingga dapat melakukan pekerjaan lain sementara itu.
Jika Anda bekerja pada utas utama aplikasi Anda, menggunakan sleep(time)buruk untuk pengalaman pengguna aplikasi Anda karena UI tidak responsif selama waktu itu.

Pengiriman setelah menjadwalkan eksekusi blok kode alih-alih membekukan utas:

Cepat ≥ 3.0

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

Cepat <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
}
Palle
sumber
1
Bagaimana dengan tindakan berkala?
qed
1
ada kemungkinan untuk menggunakan gcd untuk tugas-tugas berkala yang dijelaskan dalam artikel ini dari dokumentasi pengembang apel tetapi saya akan merekomendasikan menggunakan NSTimer untuk itu. jawaban ini menunjukkan bagaimana melakukan hal itu dengan cepat.
Palle
2
Kode diperbarui untuk Xcode 8.2.1. Sebelumnya .now() + .seconds(4)memberi kesalahan:expression type is ambiguous without more context
richards
Bagaimana saya bisa menangguhkan fungsi yang dipanggil dari antrian? @Palle
Mukul More
14
Anda tidak perlu lagi menambahkannya .now() + .seconds(5)hanya.now() + 5
cnzac
45

Perbandingan antara berbagai pendekatan di swift 3.0

1. Tidur

Metode ini tidak memiliki panggilan balik. Letakkan kode langsung setelah baris ini untuk dieksekusi dalam 4 detik. Ini akan menghentikan pengguna dari pengulangan dengan elemen UI seperti tombol tes sampai waktu hilang. Meskipun tombolnya agak beku saat tidur, elemen lain seperti indikator aktivitas masih berputar tanpa beku. Anda tidak dapat memicu tindakan ini lagi selama tidur.

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

masukkan deskripsi gambar di sini

2. Pengiriman, Lakukan dan Timer

Tiga metode ini bekerja dengan cara yang sama, mereka semua berjalan di latar belakang dengan panggilan balik, hanya dengan sintaks yang berbeda dan fitur yang sedikit berbeda.

Pengiriman biasanya digunakan untuk menjalankan sesuatu di utas latar belakang. Ini memiliki panggilan balik sebagai bagian dari panggilan fungsi

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

Perform sebenarnya adalah timer yang disederhanakan. Ini mengatur timer dengan penundaan, dan kemudian memicu fungsi dengan pemilih.

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

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

Dan akhirnya, timer juga menyediakan kemampuan untuk mengulangi panggilan balik, yang tidak berguna dalam kasus ini

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


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

Untuk ketiga metode ini, ketika Anda mengklik tombol untuk memicu mereka, UI tidak akan membeku dan Anda diizinkan untuk mengkliknya lagi. Jika Anda mengklik tombol lagi, timer lain sudah diatur dan panggilan balik akan dipicu dua kali.

masukkan deskripsi gambar di sini

Kesimpulannya

Tidak satu pun dari empat metode ini yang bekerja dengan cukup baik sendiri. sleepakan menonaktifkan interaksi pengguna, sehingga layar " membeku " (tidak benar-benar) dan menghasilkan pengalaman pengguna yang buruk. Tiga metode lainnya tidak akan membekukan layar, tetapi Anda dapat memicu mereka beberapa kali, dan sebagian besar waktu, Anda ingin menunggu sampai Anda mendapatkan panggilan kembali sebelum mengizinkan pengguna untuk melakukan panggilan lagi.

Jadi desain yang lebih baik akan menggunakan salah satu dari tiga metode async dengan pemblokiran layar. Ketika pengguna mengklik tombol, tutup seluruh layar dengan beberapa tampilan transparan dengan indikator aktivitas berputar di atas, memberi tahu pengguna bahwa klik tombol sedang ditangani. Kemudian hapus tampilan dan indikator dalam fungsi panggilan balik, memberi tahu pengguna bahwa tindakan tersebut ditangani dengan benar, dll.

Fangming
sumber
1
Perhatikan bahwa dalam Swift 4, dengan inferensi objc dimatikan, Anda harus menambahkan @objcdi depan fungsi panggilan balik untuk perform(...)opsi. Seperti itu:@objc func callback() {
leanne
32

Saya setuju dengan Palle bahwa menggunakan dispatch_afteradalah pilihan yang baik di sini. Tapi Anda mungkin tidak suka panggilan GCD karena cukup mengganggu untuk ditulis . Alih-alih, Anda dapat menambahkan pembantu praktis ini :

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

Sekarang Anda cukup menunda kode Anda pada utas latar belakang seperti ini:

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

Menunda kode di utas utama bahkan lebih sederhana:

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

Jika Anda lebih suka Kerangka yang juga memiliki beberapa fitur yang lebih berguna maka checkout HandySwift . Anda dapat menambahkannya ke proyek Anda melalui Carthage atau Accio lalu menggunakannya persis seperti pada contoh di atas:

import HandySwift    

delay(by: .seconds(1.5)) { 
    // delayed code
}
Astaga
sumber
Ini sepertinya sangat membantu. Apakah Anda keberatan memperbarui dengan versi 3.0 yang cepat. Terima kasih
grahamcracker1234
Saya mulai memigrasi HandySwift ke Swift 3 dan berencana untuk merilis versi baru minggu depan. Lalu aku akan memperbarui skrip di atas juga. Tetap disini.
Jeehut
Migrasi HandySwift ke Swift 3 sekarang lengkap dan dirilis dalam versi 1.3.0. Saya juga baru saja memperbarui kode di atas untuk bekerja dengan Swift 3! :)
Jeehut
1
Mengapa Anda mengalikan dengan NSEC_PER_SEC kemudian membaginya lagi? Bukankah itu berlebihan? (mis. Dobel (Int64 (detik * Dobel) (NSEC_PER_SEC))) / Gandakan (NSEC_PER_SEC)) Tidak bisakah Anda menulis 'DispatchTime.now () + Gandakan (detik)?
Mark A. Donohoe
1
Terima kasih @MarqueIV, tentu saja Anda benar. Saya baru saja memperbarui kodenya. Itu hanya hasil dari konversi otomatis dari Xcode 8 dan konversi jenis diperlukan sebelum karena DispatchTimetidak tersedia di Swift 2. let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))Sebelumnya.
Jeehut
24

Anda juga dapat melakukan ini dengan Swift 3.

Lakukan fungsi setelah penundaan seperti itu.

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")
        }
Cyril
sumber
23

Di Swift 4.2 dan Xcode 10.1

Anda memiliki total 4 cara untuk menunda. Keluar dari opsi ini 1 lebih disukai untuk memanggil atau menjalankan fungsi setelah beberapa waktu. The tidur () adalah paling tidak kasus digunakan.

Pilihan 1.

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

}

Pilihan 2.

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

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

Opsi 3.

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

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

}

Opsi 4.

sleep(5)

Jika Anda ingin memanggil fungsi setelah beberapa waktu untuk mengeksekusi sesuatu jangan gunakan sleep .

iOS
sumber
19

NSTimer

Jawaban oleh @nneonneo menyarankan untuk menggunakan NSTimertetapi tidak menunjukkan bagaimana melakukannya. Ini adalah sintaks dasar:

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

Ini adalah proyek yang sangat sederhana untuk menunjukkan bagaimana itu dapat digunakan. Ketika sebuah tombol ditekan, ia memulai timer yang akan memanggil fungsi setelah penundaan setengah detik.

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

Menggunakan dispatch_time(seperti pada jawaban Palle ) adalah pilihan lain yang valid. Namun, sulit untuk membatalkan . Dengan NSTimer, untuk membatalkan acara yang tertunda sebelum itu terjadi, yang perlu Anda lakukan hanyalah menelepon

timer.invalidate()

Penggunaan sleeptidak disarankan, terutama pada utas utama, karena menghentikan semua pekerjaan yang dilakukan pada utas.

Lihat di sini untuk jawaban saya yang lebih lengkap.

Suragch
sumber
7

Coba implementasi berikut di Swift 3.0

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

Pemakaian

delayWithSeconds(1) {
   //Do something
}
Vaka
sumber
7

Anda dapat membuat ekstensi untuk menggunakan fungsi penundaan dengan mudah (Sintaks: 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)
    }
}

Cara menggunakan di UIViewController

self.delay(0.1, closure: {
   //execute code
})
Hardik Thakkar
sumber
6

Jika Anda perlu mengatur penundaan kurang dari satu detik, Anda tidak perlu mengatur parameter .seconds. Saya harap ini bermanfaat bagi seseorang.

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
        // your code hear
})
Booharin
sumber
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
    }
}
eonist
sumber
4
Melakukan DispatchQueue.main.asyncAfter(deadline: .now() + 4) {/*Do stuff*/}Mungkin lebih benar ✌️
eonist
5

Jika kode Anda sudah berjalan di utas latar, jeda utas menggunakan metode ini di Yayasan :Thread.sleep(forTimeInterval:)

Sebagai contoh:

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

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

(Lihat jawaban lain untuk saran saat kode Anda berjalan di utas utama.)

Robin Stewart
sumber
3

Untuk membuat penundaan waktu yang sederhana, Anda dapat mengimpor Darwin dan kemudian menggunakan sleep (detik) untuk melakukan penundaan. Namun, itu hanya membutuhkan waktu beberapa detik, jadi untuk pengukuran yang lebih tepat, Anda dapat mengimpor Darwin dan menggunakan sleep (sepersejuta detik) untuk pengukuran yang sangat tepat. Untuk menguji ini, saya menulis:

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

Yang mencetak, lalu menunggu selama 1 detik dan mencetak, kemudian menunggu selama 0,4 detik kemudian mencetak. Semua bekerja seperti yang diharapkan.

Ethan Wrightson
sumber
-3

ini yang paling sederhana

    delay(0.3, closure: {
        // put her any code you want to fire it with delay
        button.removeFromSuperview()   
    })
taha
sumber
2
Ini bukan bagian dari Foundation, seperti yang saya asumsikan, termasuk dalam Cocoapod 'HandySwift'. Anda harus menjelaskannya dalam jawaban Anda.
Jan Nash
Apakah swift memiliki sesuatu yang menunggu sesuatu terjadi atau tidak?
Saeed Rahmatolahi