Como criar um atraso no Swift?

262

Quero pausar meu aplicativo em um determinado momento. Em outras palavras, desejo que meu aplicativo execute o código, mas, em um determinado momento, faça uma pausa por 4 segundos e continue com o restante do código. Como posso fazer isso?

Estou usando o Swift.

Schuey999
fonte

Respostas:

271

Em vez de uma suspensão, que bloqueará seu programa se for chamado a partir do thread da interface do usuário, considere usar NSTimerou um cronômetro de despacho.

Mas, se você realmente precisar de um atraso no segmento atual:

do {
    sleep(4)
}

Isso usa a sleepfunção do UNIX.

nneonneo
fonte
4
obras dormir sem importar darwin, não sei se isso é uma mudança
Dan Beaulieu
17
usleep () também funciona, se você precisar de algo mais preciso que a resolução de 1 segundo.
Prewett
18
usleep () leva milésimos de segundo, de modo usleep (1.000.000) vai dormir durante 1 seg
Harris
40
Obrigado por manter o preâmbulo "não faça isso" curto.
Dan Rosenstark 20/03/19
2
Eu uso sleep () para simular processos em segundo plano de longa execução.
William T. Mallard
345

Usar um dispatch_afterbloco é, na maioria dos casos, melhor do que usar, sleep(time)pois o encadeamento no qual o sono é realizado é impedido de realizar outro trabalho. ao usar dispatch_aftero encadeamento em que o trabalho é trabalhado, não é bloqueado, portanto, ele pode realizar outro trabalho nesse meio tempo.
Se você estiver trabalhando no segmento principal do seu aplicativo, o uso sleep(time)é ruim para a experiência do usuário, pois a interface do usuário não responde durante esse período.

Despachar após agendar a execução de um bloco de código em vez de congelar o encadeamento:

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
}
Palle
fonte
1
E as ações periódicas?
qed
1
existem possibilidades de usar o gcd para tarefas periódicas descritas neste artigo na documentação do desenvolvedor da apple, mas eu recomendaria o uso de um NSTimer para isso. Esta resposta mostra como fazer exatamente isso rapidamente.
Palle
2
Código atualizado para o Xcode 8.2.1. Anteriormente .now() + .seconds(4)dá erro:expression type is ambiguous without more context
richards
Como posso suspender a função invocada a partir da fila? @Palle
Mukul More
14
Você não precisa adicionar .now() + .seconds(5)é simplesmente.now() + 5
cnzac
45

Comparação entre diferentes abordagens no swift 3.0

1. Dormir

Este método não tem uma chamada de retorno. Coloque os códigos diretamente após esta linha para serem executados em 4 segundos. Ele impedirá o usuário de iterar com elementos da interface do usuário, como o botão de teste, até que o tempo se esgote. Embora o botão esteja meio congelado quando o sono entra em ação, outros elementos, como o indicador de atividade, ainda estão girando sem congelar. Você não pode disparar esta ação novamente durante o sono.

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

insira a descrição da imagem aqui

2. Despacho, Execução e Temporizador

Esses três métodos funcionam da mesma forma, todos eles estão sendo executados no encadeamento em segundo plano com retornos de chamada, apenas com sintaxe diferente e recursos ligeiramente diferentes.

O despacho é comumente usado para executar algo no encadeamento em segundo plano. Possui o retorno de chamada como parte da chamada de função

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

Perform é na verdade um timer simplificado. Ele configura um timer com o atraso e, em seguida, aciona a função pelo seletor.

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

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

E, finalmente, o timer também oferece a capacidade de repetir o retorno de chamada, o que não é útil nesse caso

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


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

Para todos esses três métodos, quando você clicar no botão para acioná-los, a interface do usuário não congelará e você poderá clicar nele novamente. Se você clicar no botão novamente, outro temporizador será configurado e o retorno de chamada será acionado duas vezes.

insira a descrição da imagem aqui

Em conclusão

Nenhum dos quatro métodos funciona suficientemente bem por si só. sleepdesativará a interação do usuário, para que a tela " congele " (na verdade) e resulte em uma má experiência do usuário. Os outros três métodos não congelam a tela, mas você pode acioná-los várias vezes e, na maioria das vezes, deseja esperar até receber a chamada antes de permitir que o usuário faça a chamada novamente.

Portanto, um design melhor usará um dos três métodos assíncronos com bloqueio de tela. Quando o usuário clicar no botão, cubra a tela inteira com uma exibição translúcida com um indicador de atividade giratória na parte superior, informando ao usuário que o clique do botão está sendo manipulado. Em seguida, remova a visualização e o indicador na função de retorno de chamada, informando ao usuário que a ação foi tratada adequadamente etc.

Fangming
fonte
1
Observe que no Swift 4, com a inferência objc desativada, você precisará adicionar @objcna frente da função de retorno de chamada a perform(...)opção. Assim:@objc func callback() {
leanne 18/06
32

Concordo com Palle que o uso dispatch_afteré uma boa escolha aqui. Mas você provavelmente não gosta das chamadas do GCD, pois são muito irritantes para escrever . Em vez disso, você pode adicionar este útil auxiliar :

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

Agora você simplesmente adia seu código em um thread de segundo plano como este:

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

Atrasar o código no thread principal é ainda mais simples:

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

Se você preferir um Framework que também tenha alguns recursos mais úteis, consulte o HandySwift . Você pode adicioná-lo ao seu projeto via Carthage ou Accio e usá-lo exatamente como nos exemplos acima:

import HandySwift    

delay(by: .seconds(1.5)) { 
    // delayed code
}
Jeehut
fonte
Isso parece muito útil. Você se importa de atualizá-lo com uma versão rápida do 3.0. Graças
grahamcracker1234
Comecei a migrar o HandySwift para o Swift 3 e planejo lançar uma nova versão na próxima semana. Então eu vou atualizar o script acima também. Fique ligado.
precisa saber é
A migração do HandySwift para o Swift 3 está concluída e lançada na versão 1.3.0. Também atualizei o código acima para trabalhar com o Swift 3! :)
Jeehut 20/09/16
1
Por que você está multiplicando por NSEC_PER_SEC e depois dividindo por ele novamente? Isso não é redundante? (por exemplo, Double (Int64 (segundos * Double (NSEC_PER_SEC))) / Double (NSEC_PER_SEC)) Você não conseguiu escrever 'DispatchTime.now () + Double (segundos)?
MarkDonohoe
1
Obrigado @MarqueIV, você está certo, é claro. Acabei de atualizar o código. Era simplesmente o resultado da conversão automática do Xcode 8 e a conversão de tipo era necessária antes, pois DispatchTimenão estava disponível no Swift 2. Era let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))antes.
Jeehut
24

Você também pode fazer isso com o Swift 3.

Execute a função após um atraso como esse.

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
fonte
23

No Swift 4.2 e no Xcode 10.1

Você tem um total de 4 maneiras de adiar. Entre essas opções, é preferível chamar ou executar uma função após algum tempo. O sono () é o menor caso em uso.

Opção 1.

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

}

Opção 2.

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

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

Opção 3.

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

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

}

Opção 4.

sleep(5)

Se você quiser chamar uma função após algum tempo para executar algo, não use sleep .

iOS
fonte
19

NSTimer

A resposta de @nneonneo sugeriu o uso, NSTimermas não mostrou como fazê-lo. Esta é a sintaxe básica:

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

Aqui está um projeto muito simples para mostrar como ele pode ser usado. Quando um botão é pressionado, ele inicia um timer que chamará uma função após um atraso de meio segundo.

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

Usar dispatch_time(como na resposta de Palle ) é outra opção válida. No entanto, é difícil cancelar . Com NSTimer, para cancelar um evento atrasado antes que aconteça, tudo o que você precisa fazer é ligar

timer.invalidate()

O uso sleepnão é recomendado, especialmente no encadeamento principal, pois interrompe todo o trabalho realizado no encadeamento.

Veja aqui a minha resposta mais completa.

Suragch
fonte
7

Experimente a seguinte implementação no Swift 3.0

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

Uso

delayWithSeconds(1) {
   //Do something
}
Vakas
fonte
7

Você pode criar extensões para usar a função de atraso facilmente (sintaxe: 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)
    }
}

Como usar no UIViewController

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

Se você precisar definir um atraso menor que um segundo, não será necessário definir o parâmetro .seconds. Espero que isso seja útil para alguém.

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
        // your code hear
})
Booharin
fonte
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
    }
}
eonista
fonte
4
Fazendo DispatchQueue.main.asyncAfter(deadline: .now() + 4) {/*Do stuff*/}é provavelmente mais correto ✌️
eonist
5

Se o seu código já estiver sendo executado em um thread em segundo plano, pause o thread usando este método no Foundation :Thread.sleep(forTimeInterval:)

Por exemplo:

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

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

(Veja outras respostas para obter sugestões quando o seu código estiver sendo executado no thread principal.)

Robin Stewart
fonte
3

Para criar um atraso de tempo simples, você pode importar o Darwin e usar o modo de suspensão (segundos) para fazer o atraso. Porém, isso leva apenas alguns segundos, portanto, para medições mais precisas, você pode importar o Darwin e usar o uso do sono (milionésimos de segundo) para medições muito precisas. Para testar isso, escrevi:

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

Que imprime, aguarda 1 segundo e imprime, aguarda 0,4 seg e depois imprime. Tudo funcionou como esperado.

Ethan Wrightson
fonte
-3

este é o mais simples

    delay(0.3, closure: {
        // put her any code you want to fire it with delay
        button.removeFromSuperview()   
    })
taha
fonte
2
Isso não faz parte da Foundation, está, como presumo, incluído no Cocoapod 'HandySwift'. Você deve esclarecer isso em sua resposta.
Jan Nash
O swift tem algo que espera que algo aconteça ou não?
Saeed Rahmatolahi