Як створити затримку в Swift?


262

Я хочу призупинити свою програму в певний момент. Іншими словами, я хочу, щоб моя програма виконувала код, але потім у певний момент зробіть паузу на 4 секунди, а потім продовжуйте роботу з рештою коду. Як я можу це зробити?

Я використовую Swift.


Відповіді:


270

Замість сну, який заблокує вашу програму, якщо вона буде викликана з потоку користувальницького інтерфейсу, подумайте про використання NSTimerабо таймер відправки.

Але, якщо вам дійсно потрібна затримка поточного потоку:

do {
    sleep(4)
}

Для цього використовується sleepфункція UNIX.


4
сон працює без імпорту Дарвіна, не впевнений, чи це зміна
Дан Болье

17
usleep () також працює, якщо вам потрібно щось точніше, ніж роздільна здатність 1 сек.
prewett

18
usleep () займає мільйонні частки секунди, тому usleep (1000000) буде спати протягом 1 секунди
Харріс

40
Дякуємо за те, що преамбула "Не роби цього" є короткою.
Дан Розенстарк

2
Я використовую сон () для імітації тривалих фонових процесів.
Вільям Т. Маллард

345

Використовувати dispatch_afterблок в більшості випадків краще, ніж використовувати, sleep(time)оскільки нитка, на якій виконується сон, забороняється виконувати інші роботи. при використанні dispatch_afterпотоку, над яким працює, не блокується, тим часом він може виконувати іншу роботу.
Якщо ви працюєте над основною темою програми, використання sleep(time)файлів погано впливає на користування вашим додатком, оскільки інтерфейс користувача не реагує на цей час.

Відправте після планування виконання блоку коду замість заморожування потоку:

Швидкий ≥ 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 для періодичних завдань, які описані в цій статті з документації на розробник яблук, але я рекомендую використовувати для цього NSTimer. ця відповідь показує, як зробити це саме швидко.
Палле

2
Код оновлено для Xcode 8.2.1. Раніше .now() + .seconds(4)дає помилку:expression type is ambiguous without more context
richards

Як я можу призупинити виклик функції з черги? @Palle
Mukul Ще

14
Вам більше не потрібно додавати .now() + .seconds(5)це просто.now() + 5
cnzac

45

Порівняння між різними підходами у швидкому 3.0

1. Сон

Цей метод не має зворотного дзвінка. Покладіть коди безпосередньо після того, як цей рядок буде виконано за 4 секунди. Це зупинить користувача від ітерації з елементами інтерфейсу, як тестова кнопка, поки час не пройде. Хоча кнопка замерзла, коли починається сон, інші елементи, як-от індикатор активності, все ще крутяться без замерзання. Ви не можете повторити цю дію під час сну.

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

Для всіх цих трьох методів, коли ви натискаєте кнопку, щоб запустити їх, інтерфейс не замерзне, і вам дозволяється натискати на нього ще раз. Якщо ви знову натиснете на кнопку, встановиться інший таймер, і зворотний виклик буде спрацьовано двічі.

введіть тут опис зображення

На закінчення

Жоден із чотирьох методів не працює досить добре, лише сам по собі. sleepвимкне взаємодію з користувачем, тому екран " замерзає " (насправді) і призводить до поганої роботи користувача. Інші три способи не заморожують екран, але ви можете їх запускати кілька разів, і більшість разів потрібно зачекати, поки ви отримаєте зворотній дзвінок, перш ніж дозволити користувачеві здійснити дзвінок знову.

Таким чином, кращим дизайном буде використання одного з трьох методів асинхронізації з блокуванням екрана. Коли користувач натискає кнопку, накрийте весь екран деяким напівпрозорим видом із вгорі індикатором активності обертання, повідомляючи користувачеві, що натискання кнопки обробляється. Потім видаліть подання та індикатор у функції зворотного дзвінка, повідомивши користувачеві, що дія належним чином виконується тощо.


1
Зауважте, що в Swift 4, з вимкненням об'єкта, вам потрібно буде додати @objcперед функцією зворотного виклику perform(...)опцію. @objc func callback() {
Ось

32

Я згоден з Палле, що використання 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
}

Якщо ви віддаєте перевагу Framework, який також має деякі зручніші функції, то замовіть 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 (секунди)?
Марк А. Донохое

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(як у відповіді Палле ) - ще один вірний варіант. Однак скасувати це важко . З 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

Якщо вам потрібно встановити затримку менше секунди, не потрібно встановлювати параметр .seconds. Сподіваюся, це комусь корисно.

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*/}це, мабуть, правильніше ✌️
еоніст

5

Якщо ваш код уже працює у фоновому потоці, призупиніть потік, використовуючи цей метод у Foundation :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

Щоб створити просту затримку в часі, ви можете імпортувати Дарвін, а потім скористатися сном (секундами), щоб зробити затримку. Це займає лише цілі секунди, тому для більш точних вимірювань ви можете імпортувати Дарвіна і використовувати сплячий (мільйони секунди) для дуже точного вимірювання. Щоб перевірити це, я написав:

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
Це не є частиною Фундації, вона, як я припускаю, включена до Cocoapod "HandySwift". Ви повинні уточнити це у своїй відповіді.
Ян Неш

У Swift є щось, що чекає, щоб щось сталося чи ні?
Саїд Рахматолахі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.