Come si attiva un blocco dopo un ritardo, come -performSelector: withObject: afterDelay :?


734

C'è un modo per chiamare un blocco con un parametro primitivo dopo un ritardo, come usare performSelector:withObject:afterDelay:ma con un argomento come int/ double/ float?


Questo è un punto raro in cui GCD può fare qualcosa che NSOperation non può, vero?
Anonimo White

Risposte:


1175

Penso che tu stia cercando dispatch_after(). Richiede che il tuo blocco non accetti alcun parametro, ma puoi semplicemente consentire al blocco di catturare quelle variabili dal tuo ambito locale.

int parameter1 = 12;
float parameter2 = 144.1;

// Delay execution of my block for 10 seconds.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    NSLog(@"parameter1: %d parameter2: %f", parameter1, parameter2);
});

Altro: https://developer.apple.com/documentation/dispatch/1452876-dispatch_after


88
In realtà, non è vero. Gli oggetti catturati da un blocco che non sono contrassegnati come archiviati in __block vengono conservati dal blocco e vengono rilasciati dal blocco quando viene distrutto (quando il suo conteggio di conservazione va a 0). Ecco la documentazione su questo: developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…
Ryan,

9
questo dispatch_time(DISPATCH_TIME_NOW, 10ull * NSEC_PER_SEC)frammento è cattivo. Non c'è un modo più pulito per questo?
Samvermette,

7
Sì, dispatch_get_current_queue()restituisce sempre la coda da cui viene eseguito il codice. Quindi quando questo codice viene eseguito dal thread principale, il blocco verrà eseguito anche sul thread principale.
Ryan,

20
dispatch_get_current_queue()è deprecato ora
Matej

9
Oltre a NSEC_PER_SEC, esiste anche NSEC_PER_MSEC, nel caso in cui si desideri specificare i millisecondi;)
cprcrack

504

Puoi usare dispatch_afterper chiamare un blocco in seguito. In Xcode, inizia a digitare dispatch_aftere premi Enterper completare automaticamente quanto segue:

inserisci qui la descrizione dell'immagine

Ecco un esempio con due float come "argomenti". Non devi fare affidamento su alcun tipo di macro e l'intento del codice è abbastanza chiaro:

Swift 3, Swift 4

let time1 = 8.23
let time2 = 3.42

// Delay 2 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
    print("Sum of times: \(time1 + time2)")
}

Swift 2

let time1 = 8.23
let time2 = 3.42

// Delay 2 seconds
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2.0 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
        println("Sum of times: \(time1 + time2)")
}

Obiettivo C

CGFloat time1 = 3.49;
CGFloat time2 = 8.13;

// Delay 2 seconds
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    CGFloat newTime = time1 + time2;
    NSLog(@"New time: %f", newTime);
});

45
Fai attenzione che il ritardo non sia doppio. Quindi non provare NSEC_PER_SEC * 0,5 per mezzo secondo che non funzionerà! Devi passare a millisecondi e usare NSEC_PER_MSEC * 500. Quindi dovresti cambiare il tuo codice di esempio in: int delayInSeconds = 2 per mostrare alle persone che non possono usare le frazioni di NSEC_PER_SEC.
Malhal

11
@malhal In realtà, NSEC_PER_SEC * 0.5avrebbe funzionato come NSEC_PER_MSEC * 500. Mentre hai ragione a notare che si dispatch_timeaspetta un numero intero a 64 bit, il valore che si aspetta è in nanosecondi. NSEC_PER_SECè definito come 1000000000ulle moltiplicando quello con una costante a virgola mobile si 0.5comporterebbe implicitamente un'aritmetica a virgola mobile, cedendo 500000000.0, prima che venga esplicitamente ricondotto a un numero intero a 64 bit. Quindi è perfettamente accettabile usare una frazione di NSEC_PER_SEC.
Junjie,

202

Che ne dici di usare la libreria di snippet di codice integrata Xcode?

inserisci qui la descrizione dell'immagine

Aggiornamento per Swift:

Molti voti positivi mi hanno ispirato ad aggiornare questa risposta.

La libreria di snippet di codice Xcode integrata ha dispatch_aftersolo la objective-clingua. Le persone possono anche creare il proprio snippet di codice personalizzato per Swift.

Scrivi questo in Xcode.

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(<#delayInSeconds#> * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), {
        <#code to be executed after a specified delay#>
    })

Trascina questo codice e rilascialo nell'area della libreria dello snippet di codice. inserisci qui la descrizione dell'immagine

In fondo all'elenco dei frammenti di codice, ci sarà una nuova entità denominata My Code Snippet. Modifica questo per un titolo. Per suggerimenti durante la digitazione del codice X, compilare il Completion Shortcut.

Per ulteriori informazioni, consultare CreatingaCustomCodeSnippet .

Aggiorna Swift 3

Trascina questo codice e rilascialo nell'area della libreria dello snippet di codice.

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(<#delayInSeconds#>)) {
    <#code to be executed after a specified delay#>
}

19
Qualcuno usa davvero questa funzione in Xcode? Preferisco semplicemente digitarlo come popup dei suggerimenti sul codice e sono altrettanto facili da usare.
Supertecnoboff,

6
Fino a che non sapevo di aver pensato che il copia e incolla fosse il modo più semplice per programmare. Ora
trascino

58

Espandendo la risposta di Jaime Cham, ho creato una categoria NSObject + Blocks come di seguito. Ho ritenuto che questi metodi corrispondessero meglio ai performSelector:metodi NSObject esistenti

NSObject + Blocks.h

#import <Foundation/Foundation.h>

@interface NSObject (Blocks)

- (void)performBlock:(void (^)())block afterDelay:(NSTimeInterval)delay;

@end

NSObject + Blocks.m

#import "NSObject+Blocks.h"

@implementation NSObject (Blocks)

- (void)performBlock:(void (^)())block
{
    block();
}

- (void)performBlock:(void (^)())block afterDelay:(NSTimeInterval)delay
{
    void (^block_)() = [block copy]; // autorelease this if you're not using ARC
    [self performSelector:@selector(performBlock:) withObject:block_ afterDelay:delay];
}

@end

e usare così:

[anyObject performBlock:^{
    [anotherObject doYourThings:stuff];
} afterDelay:0.15];

5
Il delaydovrebbe essere di NSTimeInterval(che è una double). #import <UIKit/UIKit.h>non è necessario. E non vedo perché - (void)performBlock:(void (^)())block;potrebbe essere utile, quindi può essere rimosso dall'intestazione.
significato-importa

@ significato-argomenti, entrambi i punti validi +1, ho aggiornato la mia risposta di conseguenza.
Oliver Pearmain

questo non è affatto corretto, performSelector deve essere rimosso esplicitamente su dealloc, altrimenti ti imbatterai in comportamenti e crash davvero strani, più corretto è usare dispatch_after
Peter Lapisu,

21

Forse più semplice che passare attraverso GCD, in una classe da qualche parte (ad esempio "Util") o una categoria su oggetto:

+ (void)runBlock:(void (^)())block
{
    block();
}
+ (void)runAfterDelay:(CGFloat)delay block:(void (^)())block 
{
    void (^block_)() = [[block copy] autorelease];
    [self performSelector:@selector(runBlock:) withObject:block_ afterDelay:delay];
}

Quindi per usare:

[Util runAfterDelay:2 block:^{
    NSLog(@"two seconds later!");
}];

3
@Jaimie Cham Perché pensi che passare attraverso GCD sia difficile?
Besi,

2
Passare attraverso GCD ha un comportamento leggermente diverso rispetto a PerformSelector: afterDelay :, quindi potrebbero esserci motivi per non usare GCD. Si veda, ad esempio, la seguente questione: stackoverflow.com/questions/10440412/...
fishinear

Perché copi il blocco prima di passarlo a performSelector?
c roald

1
Scusa per il ritardo. @croald: penso che tu abbia bisogno della copia per spostare il blocco dallo stack all'heap.
Jaime Cham,

@Besi: più prolisso e nasconde l'intento.
Jaime Cham

21

Per Swift ho creato una funzione globale, niente di speciale, usando il dispatch_aftermetodo. Mi piace di più in quanto è leggibile e facile da usare:

func performBlock(block:() -> Void, afterDelay delay:NSTimeInterval){
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), block)
}

Che puoi usare come segue:

performBlock({ () -> Void in
    // Perform actions
}, afterDelay: 0.3)

1
Suggerisco di scambiare argomenti e rinominarlo after. Quindi puoi scrivere:after(2.0){ print("do somthing") }
Lars Blumberg l'

16

Ecco i miei 2 centesimi = 5 metodi;)

Mi piace incapsulare questi dettagli e AppCode mi dice come finire le mie frasi.

void dispatch_after_delay(float delayInSeconds, dispatch_queue_t queue, dispatch_block_t block) {
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, queue, block);
}

void dispatch_after_delay_on_main_queue(float delayInSeconds, dispatch_block_t block) {
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_after_delay(delayInSeconds, queue, block);
}

void dispatch_async_on_high_priority_queue(dispatch_block_t block) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), block);
}

void dispatch_async_on_background_queue(dispatch_block_t block) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), block);
}

void dispatch_async_on_main_queue(dispatch_block_t block) {
    dispatch_async(dispatch_get_main_queue(), block);
}

8

PerformSelector: WithObject prende sempre un oggetto, quindi per passare argomenti come int / double / float ecc ..... Puoi usare qualcosa del genere.

// NSNumber è un oggetto ..

[self performSelector:@selector(setUserAlphaNumber:)
     withObject: [NSNumber numberWithFloat: 1.0f]       
     afterDelay:1.5];



-(void) setUserAlphaNumber: (NSNumber*) number{

     [txtUsername setAlpha: [number floatValue] ];

}

Allo stesso modo puoi usare [NSNumber numberWithInt:] ecc .... e nel metodo di ricezione puoi convertire il numero nel tuo formato come [numero int] o [numero doppio].


8

La funzione dispatch_after invia un oggetto a blocchi in una coda di invio dopo un determinato periodo di tempo. Utilizzare il codice seguente per eseguire alcuni tak relativi all'interfaccia utente dopo 2,0 secondi.

            let delay = 2.0
            let delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)))
            let mainQueue = dispatch_get_main_queue()

            dispatch_after(delayInNanoSeconds, mainQueue, {

                print("Some UI related task after delay")
            })

In rapido 3.0:

            let dispatchTime: DispatchTime = DispatchTime.now() + Double(Int64(2.0 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
            DispatchQueue.main.asyncAfter(deadline: dispatchTime, execute: {

          })

c'è un refuso: mainQueue, invece dimainQueue)
Bastian

5

Ecco il modo Swift 3 per mettere in coda il lavoro dopo un ritardo.

DispatchQueue.main.asyncAfter(
  DispatchTime.now() + DispatchTimeInterval.seconds(2)) {
    // do work
}

5

Ecco un utile aiuto per evitare di fare ripetutamente la fastidiosa chiamata 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)
        }
    }
}

Ora puoi semplicemente ritardare il tuo codice sul thread principale in questo modo:

delay(bySeconds: 1.5) { 
    // delayed code
}

Se vuoi ritardare il tuo codice su thread diversi :

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

Se preferisci un Framework che ha anche alcune funzioni più utili, dai un'occhiata a HandySwift . Puoi aggiungerlo al tuo progetto tramite Cartagine e usarlo esattamente come negli esempi sopra:

import HandySwift    

delay(bySeconds: 1.5) { 
    // delayed code
}

Ciò è implicito che la funzione di ritardo esegue il codice dal thread in background. Qualcuno che usa il tuo esempio può avere tempi davvero difficili per il debug dell'app in crash, se inserisce un codice relativo all'interfaccia utente all'interno della sezione // codice ritardato .
nalexn,

Di default il mio metodo usa il thread principale in modo che ciò non dovrebbe accadere. Vedi il dispatchLevel predefinito su .Main?
Jeehut,


4

In swift 3, possiamo semplicemente usare la funzione DispatchQueue.main.asyncAfter per attivare qualsiasi funzione o azione dopo il ritardo di 'n' secondi. Qui nel codice abbiamo impostato il ritardo dopo 1 secondo. Chiamate qualsiasi funzione all'interno del corpo di questa funzione che si attiverà dopo il ritardo di 1 secondo.

let when = DispatchTime.now() + 1
DispatchQueue.main.asyncAfter(deadline: when) {

    // Trigger the function/action after the delay of 1Sec

}

4

Xcode 10.2 e Swift 5 e versioni successive

DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
   // code to execute                 
})

1

Puoi racchiudere l'argomento nella tua classe o avvolgere la chiamata del metodo in un metodo che non deve essere passato nel tipo primitivo. Quindi chiama quel metodo dopo il tuo ritardo e, all'interno di quel metodo, esegui il selettore che desideri eseguire.


1

Ecco come è possibile attivare un blocco dopo un ritardo in Swift:

runThisAfterDelay(seconds: 2) { () -> () in
    print("Prints this 2 seconds later in main queue")
}

/// EZSwiftExtensions
func runThisAfterDelay(seconds seconds: Double, after: () -> ()) {
    let time = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))
    dispatch_after(time, dispatch_get_main_queue(), after)
}

È incluso come funzione standard nel mio repository .


1

Swift 3 e Xcode 8.3.2

Questo codice ti aiuterà, aggiungo anche una spiegazione

// Create custom class, this will make your life easier
class CustomDelay {

    static let cd = CustomDelay()

    // This is your custom delay function
    func runAfterDelay(_ delay:Double, closure:@escaping ()->()) {
        let when = DispatchTime.now() + delay
        DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
    }
}


// here how to use it (Example 1)
class YourViewController: UIViewController {

    // example delay time 2 second
    let delayTime = 2.0

    override func viewDidLoad() {
        super.viewDidLoad()

        CustomDelay.cd.runAfterDelay(delayTime) {
            // This func will run after 2 second
            // Update your UI here, u don't need to worry to bring this to the main thread because your CustomDelay already make this to main thread automatically :)
            self.runFunc()
        }
    }

    // example function 1
    func runFunc() {
        // do your method 1 here
    }
}

// here how to use it (Example 2)
class YourSecondViewController: UIViewController {

    // let say you want to user run function shoot after 3 second they tap a button

    // Create a button (This is programatically, you can create with storyboard too)
    let shootButton: UIButton = {
        let button = UIButton(type: .system)
        button.frame = CGRect(x: 15, y: 15, width: 40, height: 40) // Customize where do you want to put your button inside your ui
        button.setTitle("Shoot", for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        // create an action selector when user tap shoot button
        shootButton.addTarget(self, action: #selector(shoot), for: .touchUpInside)   
    }

    // example shoot function
    func shoot() {
        // example delay time 3 second then shoot
        let delayTime = 3.0

        // delay a shoot after 3 second
        CustomDelay.cd.runAfterDelay(delayTime) {
            // your shoot method here
            // Update your UI here, u don't need to worry to bring this to the main thread because your CustomDelay already make this to main thread automatically :)
        }
    }   
}

0

Credo che l'autore non stia chiedendo come aspettare un tempo frazionario (ritardo), ma invece come passare uno scalare come argomento del selettore (conOggetto :) e il modo più veloce nel moderno obiettivo C è:

[obj performSelector:...  withObject:@(0.123123123) afterDelay:10]

il tuo selettore deve cambiare il suo parametro in NSNumber e recuperare il valore usando un selettore come floatValue o doubleValue

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.