Wie löst man einen Block nach einer Verzögerung aus, wie -performSelector: withObject: afterDelay:?

735

Gibt es eine Möglichkeit, einen Block mit einem primitiven Parameter nach einer Verzögerung aufzurufen, z. B. performSelector:withObject:afterDelay:mit einem Argument wie int/ double/ float?

Egil
quelle
Dies ist ein seltener Punkt, an dem GCD etwas kann, was NSOperation nicht kann, nicht wahr?
Anonym Weiß

Antworten:

1175

Ich denke du suchst dispatch_after(). Es erfordert, dass Ihr Block keine Parameter akzeptiert, aber Sie können den Block stattdessen einfach diese Variablen aus Ihrem lokalen Bereich erfassen lassen.

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

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

Ryan
quelle
88
Eigentlich stimmt das nicht. Objekte, die von einem Block erfasst wurden, der nicht als __block-Speicher markiert ist, werden vom Block beibehalten und vom Block freigegeben, wenn er zerstört wird (wenn seine Aufbewahrungsanzahl auf 0 geht). Hier ist die Dokumentation dazu: developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…
Ryan
9
Dieser dispatch_time(DISPATCH_TIME_NOW, 10ull * NSEC_PER_SEC)Ausschnitt ist böse. Gibt es dafür keinen saubereren Weg?
Samvermette
7
Ja, gibt dispatch_get_current_queue()immer die Warteschlange zurück, aus der der Code ausgeführt wird. Wenn dieser Code vom Hauptthread ausgeführt wird, wird der Block auch im Hauptthread ausgeführt.
Ryan
20
dispatch_get_current_queue()ist jetzt veraltet
Matej
9
Neben NSEC_PER_SEC gibt es auch NSEC_PER_MSEC, falls Sie Millisekunden angeben möchten;)
cprcrack
504

Sie können dispatch_afterspäter einen Block aufrufen. Beginnen Sie in Xcode mit der Eingabe dispatch_afterund Enterdrücken Sie, um die folgenden Schritte automatisch zu vervollständigen:

Geben Sie hier die Bildbeschreibung ein

Hier ist ein Beispiel mit zwei Gleitkommazahlen als "Argumente". Sie müssen sich nicht auf irgendeine Art von Makro verlassen, und die Absicht des Codes ist ganz klar:

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

Ziel 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);
});
Steven Hepting
quelle
45
Achten Sie darauf, dass die Verzögerungszeit nicht doppelt so hoch ist. Versuchen Sie es also nicht eine halbe Sekunde lang mit NSEC_PER_SEC * 0.5, es wird nicht funktionieren! Sie müssen auf Millisekunden fallen und NSEC_PER_MSEC * 500 verwenden. Ändern Sie daher Ihr Codebeispiel in: int delayInSeconds = 2, um anzuzeigen, dass Benutzer keine Bruchteile von NSEC_PER_SEC verwenden können.
Malhal
11
@malhal Eigentlich NSEC_PER_SEC * 0.5würde das genauso funktionieren wie NSEC_PER_MSEC * 500. Während Sie zu Recht feststellen, dispatch_timedass eine 64-Bit-Ganzzahl erwartet wird, wird der erwartete Wert in Nanosekunden angegeben. NSEC_PER_SECist definiert als 1000000000ullund das Multiplizieren mit einer Gleitkommakonstante 0.5würde implizit eine Gleitkomma-Arithmetik ausführen, die sich ergibt 500000000.0, bevor sie explizit in eine 64-Bit-Ganzzahl zurückgesetzt wird. Es ist also durchaus akzeptabel, einen Bruchteil von zu verwenden NSEC_PER_SEC.
Juni
202

Wie wäre es mit der in Xcode integrierten Code-Snippet-Bibliothek?

Geben Sie hier die Bildbeschreibung ein

Update für Swift:

Viele Stimmen haben mich dazu inspiriert, diese Antwort zu aktualisieren.

Die integrierte Xcode-Code-Snippet-Bibliothek ist dispatch_afternur für die objective-cSprache verfügbar . Benutzer können auch ihr eigenes benutzerdefiniertes Code-Snippet für erstellen Swift.

Schreiben Sie dies 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#>
    })

Ziehen Sie diesen Code und legen Sie ihn im Bereich der Code-Snippet-Bibliothek ab. Geben Sie hier die Bildbeschreibung ein

Am Ende der Code-Snippet-Liste wird eine neue Entität mit dem Namen angezeigt My Code Snippet. Bearbeiten Sie dies für einen Titel. Für Vorschläge, während Sie den Xcode eingeben, füllen Sie das Feld aus Completion Shortcut.

Weitere Informationen finden Sie unter Erstellen eines CustomCodeSnippets .

Aktualisieren Sie Swift 3

Ziehen Sie diesen Code und legen Sie ihn im Bereich der Code-Snippet-Bibliothek ab.

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(<#delayInSeconds#>)) {
    <#code to be executed after a specified delay#>
}
Warif Akhand Rishi
quelle
19
Verwendet jemand diese Funktion tatsächlich in Xcode? Ich ziehe es vor, es einfach als Popup für Codevorschläge einzugeben und bin genauso einfach zu bedienen.
Supertecnoboff
6
Bis ich wusste, dass Copy & Paste der einfachste Weg ist, Code zu schreiben. Jetzt
ziehe
58

Als Erweiterung der Antwort von Jaime Cham habe ich eine NSObject + Blocks-Kategorie wie folgt erstellt. Ich war der Meinung, dass diese Methoden besser zu den vorhandenen performSelector:NSObject-Methoden passen

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

und benutze so:

[anyObject performBlock:^{
    [anotherObject doYourThings:stuff];
} afterDelay:0.15];
Oliver Pearmain
quelle
5
Das delaysollte von sein NSTimeInterval(was a ist double). #import <UIKit/UIKit.h>wird nicht gebraucht. Und ich verstehe nicht, warum - (void)performBlock:(void (^)())block;nützlich sein könnte, kann also aus dem Header entfernt werden.
Sinn-Angelegenheiten
@ Bedeutung-Angelegenheiten, beide gültige Punkte +1, ich habe meine Antwort entsprechend aktualisiert.
Oliver Pearmain
Dies ist überhaupt nicht korrekt, der performSelector muss explizit auf Dealloc entfernt werden, sonst stoßen Sie auf wirklich seltsames Verhalten und Abstürze. Richtiger ist die Verwendung von dispatch_after
Peter Lapisu
21

Vielleicht einfacher als GCD zu durchlaufen, in einer Klasse irgendwo (zB "Util") oder in einer Kategorie nach Objekt:

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

Also zu benutzen:

[Util runAfterDelay:2 block:^{
    NSLog(@"two seconds later!");
}];
Jaime Cham
quelle
3
@Jaimie Cham Warum denkst du, ist es schwierig, GCD zu durchlaufen?
Besi
2
Das Durchlaufen von GCD hat ein etwas anderes Verhalten als PerformSelector: afterDelay:, daher kann es Gründe geben, GCD nicht zu verwenden. Siehe zum Beispiel die folgende Frage: stackoverflow.com/questions/10440412/…
fishinear
Warum kopieren Sie den Block, bevor Sie ihn an performSelector übergeben?
C Roald
1
Entschuldigung für die Verspätung. @ Croald: Ich denke, Sie brauchen die Kopie, um den Block vom Stapel auf den Heap zu verschieben.
Jaime Cham
@Besi: wortreicher und verbirgt die Absicht.
Jaime Cham
21

Für Swift habe ich mit dieser dispatch_afterMethode eine globale Funktion erstellt, die nichts Besonderes ist . Ich mag das mehr, da es lesbar und einfach zu bedienen ist:

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

Welche können Sie wie folgt verwenden:

performBlock({ () -> Void in
    // Perform actions
}, afterDelay: 0.3)
Antoine
quelle
1
Ich schlage vor, Argumente auszutauschen und in umzubenennen after. Dann können Sie schreiben:after(2.0){ print("do somthing") }
Lars Blumberg
16

Hier sind meine 2 Cent = 5 Methoden;)

Ich mag es, diese Details zu kapseln und AppCode sagt mir, wie ich meine Sätze beenden soll.

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

PerformSelector: WithObject nimmt immer ein Objekt, um Argumente wie int / double / float usw. zu übergeben. Sie können so etwas verwenden.

// NSNumber ist ein Objekt ..

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



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

     [txtUsername setAlpha: [number floatValue] ];

}

Auf die gleiche Weise können Sie [NSNumber numberWithInt:] etc .... verwenden und in der Empfangsmethode die Nummer in Ihr Format als [number int] oder [number double] konvertieren.

Augustinus
quelle
8

Die Funktion dispatch_after sendet nach einer bestimmten Zeit ein Blockobjekt an eine Versandwarteschlange. Verwenden Sie den folgenden Code, um nach 2,0 Sekunden einige UI-bezogene Takes durchzuführen.

            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 schnellem 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: {

          })
Himanshu Mahajan
quelle
Es gibt einen Tippfehler: mainQueue, anstelle vonmainQueue)
Bastian
5

Hier ist die Swift 3-Methode, um die Arbeit nach einer Verzögerung in die Warteschlange zu stellen.

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

Hier ist ein praktischer Helfer , um zu verhindern, dass der nervige GCD immer wieder aufgerufen wird:

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

Jetzt verzögern Sie einfach Ihren Code im Haupt-Thread wie folgt:

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

Wenn Sie Ihren Code auf einen anderen Thread verschieben möchten :

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

Wenn Sie ein Framework bevorzugen , das auch einige weitere praktische Funktionen bietet, lesen Sie HandySwift . Sie können es über Karthago zu Ihrem Projekt hinzufügen und dann genau wie in den obigen Beispielen verwenden:

import HandySwift    

delay(bySeconds: 1.5) { 
    // delayed code
}
Jeehut
quelle
Dies impliziert, dass Ihre Verzögerungsfunktion Code aus dem Hintergrundthread ausführt. Jemand, der Ihr Beispiel verwendet, kann es sehr schwer haben, die abgestürzte App zu debuggen, wenn er einen UI-bezogenen Code in den // verzögerten Codeabschnitt einfügt.
Nalexn
Standardmäßig verwendet meine Methode den Hauptthread, damit dies nicht passieren sollte. Siehe dispatchLevel standardmäßig .Main?
Jeehut
4

Es gibt eine schöne im BlocksKit-Framework.

BlocksKit

(und die Klasse)

BBlocksKit.m

psy
quelle
4

In Swift 3 können wir einfach die Funktion DispatchQueue.main.asyncAfter verwenden, um eine Funktion oder Aktion nach der Verzögerung von 'n' Sekunden auszulösen. Hier im Code haben wir die Verzögerung nach 1 Sekunde eingestellt. Sie rufen jede Funktion im Hauptteil dieser Funktion auf, die nach einer Verzögerung von 1 Sekunde ausgelöst wird.

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

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

}
Rouny
quelle
4

Xcode 10.2 und Swift 5 und höher

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

Sie können das Argument entweder in Ihre eigene Klasse einschließen oder den Methodenaufruf in eine Methode einschließen, die nicht im primitiven Typ übergeben werden muss. Rufen Sie diese Methode nach Ihrer Verzögerung auf und führen Sie innerhalb dieser Methode den Selektor aus, den Sie ausführen möchten.

GendoIkari
quelle
1

So können Sie nach einer Verzögerung in Swift einen Block auslösen:

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

Es ist als Standardfunktion in meinem Repo enthalten .

Esqarrouth
quelle
1

Swift 3 & Xcode 8.3.2

Dieser Code wird Ihnen helfen, ich füge auch eine Erklärung hinzu

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

Ich glaube, der Autor fragt nicht, wie man auf einen Bruchteil der Zeit wartet (Verzögerung), sondern wie man einen Skalar als Argument des Selektors übergibt (withObject :) und der schnellste Weg im modernen Ziel C ist:

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

Ihr Selektor muss seinen Parameter in NSNumber ändern und den Wert mit einem Selektor wie floatValue oder doubleValue abrufen

André
quelle