Как создать задержку в Swift?
Я хочу приостановить свое приложение в определенной точке. Другими словами, я хочу, чтобы мое приложение выполняло код, но затем в определенный момент делает паузу в течение 4 секунд, а затем продолжает работу с остальной частью кода. Как я могу это сделать?
Я использую Swift.
20 ответов
Вместо сна, который заблокирует вашу программу при вызове из потока пользовательского интерфейса, рассмотрите возможность использования NSTimer
или таймер отправки.
Но, если вам действительно нужна задержка в текущем потоке:
... {
sleep(4)
}
Это использует sleep
функция из UNIX.
Используя dispatch_after
блок в большинстве случаев лучше, чем с помощью sleep(time)
поскольку поток, в котором выполняется сон, заблокирован от выполнения другой работы. когда используешь dispatch_after
работающий поток не блокируется, поэтому он может выполнять другую работу.
Если вы работаете над основным потоком вашего приложения, используя sleep(time)
плохо влияет на работу вашего приложения, так как пользовательский интерфейс в это время не отвечает.
Отправка после планирования выполнения блока кода вместо замораживания потока:
Swift ≥ 3.0
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4), execute: {
// 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
}
Сравнение разных подходов в 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
отключит взаимодействие с пользователем, поэтому экран "зависает"(не на самом деле) и приводит к плохому взаимодействию с пользователем. Три других метода не замораживают экран, но вы можете запускать их несколько раз, и в большинстве случаев вам нужно подождать, пока вы не перезвоните, прежде чем позволить пользователю повторить вызов.
Так что лучшим дизайном будет использование одного из трех асинхронных методов с блокировкой экрана. Когда пользователь нажимает кнопку, закройте весь экран некоторым полупрозрачным изображением с индикатором вращающейся активности сверху, сообщая пользователю, что нажатие кнопки обрабатывается. Затем удалите представление и индикатор в функции обратного вызова, сообщая пользователю, что действие выполнено правильно, и т. Д.
В 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)
Я согласен с Палле, что с помощью 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, а затем использовать его точно так же, как в примерах выше:
import HandySwift
delay(by: .seconds(1.5)) {
// delayed code
}
Вы также можете сделать это с помощью 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")
}
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
не рекомендуется, особенно в главном потоке, так как он останавливает всю работу, выполняемую в потоке.
Смотрите здесь для моего более полного ответа.
Я считаю, что самый простой и последний способ сделать 4-секундный таймер:
Task {
// Do something
// Wait for 4 seconds
try await Task.sleep(nanoseconds: 4_000_000_000)
}
Он использует новый параллелизм Swift 5.5.
Попробуйте следующую реализацию в Swift 3.0
func delayWithSeconds(_ seconds: Double, completion: @escaping () -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
completion()
}
}
использование
delayWithSeconds(1) {
//Do something
}
Если вам нужно установить задержку менее секунды, нет необходимости устанавливать параметр.seconds. Надеюсь это кому-нибудь пригодится.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
// your code hear
})
Вы можете легко создать расширение для использования функции задержки (Синтаксис: 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
})
Если ваш код уже выполняется в фоновом потоке, приостановите поток, используя этот метод в 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)
}
(Смотрите другие ответы для предложений, когда ваш код работает в главном потоке.)
С использованием
DispatchQueue
х
.asyncAfter
метод, вы можете выполнить код через заданное время. Так, например, выполнить
...
в основном потоке через 1 секунду выглядит так:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { ... }
Используя мой удобный
Delay
структуру оболочки, вы можете выполнить ее более причудливым способом:
struct Delay {
@discardableResult
init(_ timeInterval: TimeInterval, queue: DispatchQueue = .main, executingBlock: @escaping () -> Void) {
queue.asyncAfter(deadline: .now() + timeInterval, execute: executingBlock)
}
}
Применение:
Delay(0.4) { ... }
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
}
}
Чтобы создать простую задержку, вы можете импортировать Darwin, а затем использовать sleep(секунд), чтобы сделать задержку. Однако это занимает целые секунды, поэтому для более точных измерений вы можете импортировать Дарвин и использовать usleep(миллионные доли секунды) для очень точных измерений. Чтобы проверить это, я написал:
import Darwin
print("This is one.")
sleep(1)
print("This is two.")
usleep(400000)
print("This is three.")
Который печатает, затем ждет в течение 1 секунды и печатает, затем ждет в течение 0,4 секунды, а затем печатает. Все работало как положено.
Свифт 5<
С использованием
Task.sleep
не будет блокировать какой-либо код, кроме текущей задачи, и это довольно просто.
//Delay task by 4 seconds:
Task {
try await Task.sleep(nanoseconds: 4000000000)
//Execute your code here
}
Это более простой способ добавления задержки, которая не влияет на выполнение потока.
let continueTime: Date = Calendar.current.date(byAdding: .second, value: 30, to: Date())!
while (Date() < continueTime) {
//DO NOTHING
}
В качестве альтернативы ранее предложенным вариантам можно использовать задержку на основе
DispatchGroup
класс, который предназначен для синхронизации выполнения нескольких асинхронных задач:
print("Start")
print(Date())
let delay = DispatchTimeInterval.seconds(3)
let group = DispatchGroup()
group.enter()
_ = group.wait(timeout: .now() + delay)
print("Finish")
print(Date())
Где
enter()
Метод используется для явного указания того, что выполнение группового кода началось, и метода для ожидания завершения групповых задач. Конечно, в данном примере этого никогда не произойдет, для этого указан таймаут, равный требуемой задержке.
Его удобно использовать как готовый помощник:
public class DispatchWait {
private init () { }
public static func `for` (_ interval: DispatchTimeInterval) {
let group = DispatchGroup()
group.enter()
_ = group.wait(timeout: .now().advanced(by: interval))
}
}
Пример использования
DispatchWait
:
print("Start")
print(Date())
DispatchWait.for(.seconds(3))
print("Finish")
print(Date())
К сожалению, я не могу сказать, какова точность этой задержки, и какова вероятность того, что
wait(timeout:)
позволит продолжить выполнение программы намного позже указанной задержки.
Кроме того, это решение позволяет отложить код в текущей очереди, не выполняя его в отдельном закрытии.
let queue = DispatchQueue(label: "queue")
queue.async {
for i in 1..<100000 {
Thread.sleep(until: Date(timeIntervalSinceNow: 1)) //1 second delay
DispatchQueue.main.async {
self.label.text = "Second: \(i)"
}
}
}
Это самый простой
delay(0.3, closure: {
// put her any code you want to fire it with delay
button.removeFromSuperview()
})