Как создать задержку в Swift?

262

Я хочу приостановить свое приложение в определенной точке. Другими словами, я хочу, чтобы мое приложение выполняло код, но затем в определенный момент делает паузу в течение 4 секунд, а затем продолжает работу с остальной частью кода. Как я могу это сделать?

Я использую Swift.

Schuey999
источник
1
stackoverflow.com/a/24318861/716216
Мик МакКаллум

Ответы:

271

Вместо режима сна, который заблокирует вашу программу, если она вызывается из потока пользовательского интерфейса, рассмотрите возможность использования NSTimerили таймера отправки.

Но, если вам действительно нужна задержка в текущем потоке:

do {
    sleep(4)
}

Это использует sleepфункцию из UNIX.

nneonneo
источник
4
сон работает без импорта дарвина, не уверен, что это изменение
Дэн Болье
17
usleep () также работает, если вам нужно что-то более точное, чем разрешение в 1 секунду.
до
18
usleep () занимает миллионные доли секунды, поэтому usleep (1000000) будет спать в течение 1 секунды
Харрис
40
Спасибо за короткую преамбулу "не делай этого".
Дэн Розенстарк
2
Я использую sleep () для имитации длительных фоновых процессов.
Уильям Т. Маллард
345

Использование dispatch_afterблока в большинстве случаев лучше, чем использование, sleep(time)поскольку поток, в котором выполняется спящий режим, заблокирован от выполнения другой работы. при использовании dispatch_afterпотока, над которым работает, не блокируется, поэтому он может выполнять другую работу в то же время.
Если вы работаете с основным потоком приложения, его использование sleep(time)плохо влияет на работу вашего приложения, поскольку пользовательский интерфейс в это время не отвечает.

Отправка после планирования выполнения блока кода вместо замораживания потока:

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
Как насчет периодических действий?
16:00
1
Существуют возможности использовать gcd для периодических задач, которые описаны в этой статье из документации разработчика Apple, но я бы порекомендовал использовать для этого NSTimer. Этот ответ показывает, как сделать это быстро.
Палле
2
Код обновлен для Xcode 8.2.1. Ранее .now() + .seconds(4)выдает ошибку:expression type is ambiguous without more context
Ричардс
Как я могу приостановить функцию, вызванную из очереди? @Palle
More
14
Вам больше не нужно добавлять .now() + .seconds(5)это просто.now() + 5
cnzac
45

Сравнение разных подходов в Swift 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отключит взаимодействие с пользователем, поэтому экран « зависает » (не на самом деле) и приводит к плохому взаимодействию с пользователем. Три других метода не замораживают экран, но вы можете запускать их несколько раз, и в большинстве случаев вам нужно подождать, пока вы не перезвоните, прежде чем позволить пользователю повторить вызов.

Так что лучшим дизайном будет использование одного из трех асинхронных методов с блокировкой экрана. Когда пользователь нажимает кнопку, закройте весь экран каким-нибудь полупрозрачным изображением с вращающимся индикатором активности сверху, сообщая пользователю, что нажатие кнопки обрабатывается. Затем удалите представление и индикатор в функции обратного вызова, сообщая пользователю, что действие выполнено правильно, и т. Д.

Fangming
источник
1
Обратите внимание, что в Swift 4 с отключенным выводом objc вам необходимо добавить @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
}
Jeehut
источник
Это кажется очень полезным. Вы не против обновить его версией Swift 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 предпочтительно вызывать или выполнять функцию через некоторое время. Функция sleep () используется меньше всего.

Опция 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)

Если вы хотите вызвать функцию через некоторое время для выполнения чего-либо, не используйте sleep .

IOS
источник
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не рекомендуется, особенно в основном потоке, так как он останавливает всю работу, выполняемую в потоке.

Смотрите здесь для моего более полного ответа.

Suragch
источник
7

Попробуйте следующую реализацию в Swift 3.0

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

использование

delayWithSeconds(1) {
   //Do something
}
Vakas
источник
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
})
Booharin
источник
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
источник
4
Делать DispatchQueue.main.asyncAfter(deadline: .now() + 4) {/*Do stuff*/}, вероятно , более правильно ✌️
eonist
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

Чтобы создать простую задержку, вы можете импортировать Darwin, а затем использовать sleep (секунд), чтобы сделать задержку. Однако это занимает всего целые секунды, поэтому для более точных измерений вы можете импортировать Дарвин и использовать 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
Это не часть Foundation, а, как я полагаю, она включена в Cocoapod 'HandySwift'. Вы должны уточнить это в своем ответе.
Ян Нэш
У swift есть что-то, что ждет чего-то, или нет?
Саид Рахматолахи