web-dev-qa-db-fra.com

Comment déclencher un bloc après un délai, comme -performSelector: withObject: afterDelay :?

Existe-t-il un moyen d'appeler un bloc avec un paramètre primitif après un délai, comme avec performSelector:withObject:afterDelay: mais avec un argument tel que int/double/float?

722
Egil

Je pense que vous cherchez dispatch_after(). Cela nécessite que votre bloc n'accepte aucun paramètre, mais vous pouvez simplement laisser le bloc capturer ces variables à partir de votre portée 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);
});

Plus: https://developer.Apple.com/documentation/dispatch/1452876-dispatch_after

1158
Ryan

Vous pouvez utiliser dispatch_after pour appeler un bloc ultérieurement. Dans Xcode, commencez à taper dispatch_after et appuyez sur Enter pour compléter automatiquement les éléments suivants:

enter image description here

Voici un exemple avec deux floats comme "arguments". Vous n’avez pas à compter sur aucun type de macro, et l’intention du code est claire:

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

Objectif 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);
});
487
Steven Hepting

Que diriez-vous d'utiliser la bibliothèque d'extraits de code intégrée à Xcode?

enter image description here

Mise à jour pour Swift:

De nombreux votes m'ont inspiré pour mettre à jour cette réponse.

La bibliothèque de fragments de code Xcode intégrée contient dispatch_after pour le langage objective-c uniquement. Les utilisateurs peuvent également créer leur propre extrait de code personnalisé pour Swift.

Ecrivez ceci dans 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#>
    })

Faites glisser ce code et déposez-le dans la zone de la bibliothèque d'extraits de code. enter image description here

En bas de la liste des extraits de code, il y aura une nouvelle entité nommée My Code Snippet. Modifiez ceci pour un titre. Pour suggestion lorsque vous tapez le Xcode, renseignez le Completion Shortcut.

Pour plus d'informations, voir CreatingaCustomCodeSnippet .

Mettre à jour Swift 3

Faites glisser ce code et déposez-le dans la zone de la bibliothèque d'extraits de code.

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

En développant la réponse de Jaime Cham, j'ai créé une catégorie NSObject + Blocks comme ci-dessous. Je pensais que ces méthodes correspondaient mieux aux méthodes performSelector: NSObject existantes

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

et utiliser comme ça:

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

Peut-être plus simple que de passer par GCD, dans une classe quelque part (par exemple, "Util") ou une catégorie sur un objet:

+ (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];
}

Donc, pour utiliser:

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

Pour Swift j'ai créé une fonction globale, rien de spécial en utilisant la méthode dispatch_after. J'aime plus cela car il est lisible et facile à utiliser:

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

Que vous pouvez utiliser comme suit:

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

Voici mes 2 cents = 5 méthodes;)

J'aime encapsuler ces détails et qu'AppCode me dise comment terminer mes phrases.

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);
}
16
Dan Rosenstark

La fonction dispatch_after distribue un objet de bloc dans une file d'attente de distribution après une période donnée. Utilisez le code ci-dessous pour effectuer certaines tâches liées à l’UI après 2,0 secondes.

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

Dans Swift 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: {

          })
8
Himanshu Mahajan

PerformSelector: WithObject prend toujours un objet, donc pour passer des arguments comme int/double/float etc ..... Vous pouvez utiliser quelque chose comme ceci.

// NSNumber est un objet.

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



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

     [txtUsername setAlpha: [number floatValue] ];

}

De la même manière, vous pouvez utiliser [NSNumber numberWithInt:] etc .... et dans la méthode de réception, vous pouvez convertir le nombre dans votre format sous le format [nombre entier] ou [nombre double].

8
Augustine

Voici le Swift 3 moyen de mettre en file d'attente les travaux après un certain délai.

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

Voici un assistant pratique pour éviter de répéter sans cesse l'appel gênant de 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)
        }
    }
}

Maintenant, vous simplement retardez votre code sur le fil principal comme ceci:

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

Si vous voulez différer votre code vers un autre thread :

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

Si vous préférez un Framework qui présente également des fonctionnalités plus pratiques, cliquez sur checkout HandySwift. Vous pouvez l'ajouter à votre projet via Carthage puis l'utiliser exactement comme dans les exemples ci-dessus:

import HandySwift    

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

Dans Swift 3, nous pouvons simplement utiliser la fonction DispatchQueue.main.asyncAfter pour déclencher une fonction ou une action après le délai de 'n' secondes. Ici, dans le code, nous avons défini le délai après 1 seconde. Vous appelez n'importe quelle fonction dans le corps de cette fonction qui se déclenchera après le délai de 1 seconde.

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

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

}
4
Rouny

Il y en a un de Nice dans le framework BlocksKit.

BlocksKit

(et la classe)

BBlocksKit.m

4
psy

Vous pouvez envelopper l'argument dans votre propre classe ou insérer l'appel de méthode dans une méthode qui n'a pas besoin d'être passée dans le type primitif. Appelez ensuite cette méthode après votre délai et dans cette méthode, effectuez le sélecteur que vous souhaitez effectuer.

1
GendoIkari

Voici comment déclencher un blocage après un délai dans 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)
}

Il est inclus en tant que fonction standard dans my repo .

1
Esqarrouth

Swift 3 & Xcode 8.3.2

Ce code vous aidera, j'ajoute aussi une explication

// 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 :)
        }
    }   
}
1
Andrian Rahardja

Je crois que l'auteur ne demande pas comment attendre un délai fractionnaire, mais comment passer un scalaire en tant qu'argument du sélecteur (withObject :) et le moyen le plus rapide dans l'Object C moderne est:

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

votre sélecteur doit changer son paramètre en NSNumber et récupérer la valeur à l'aide d'un sélecteur tel que floatValue ou doubleValue

0
André

Xcode 10.2 et Swift 5 et plus

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