Jak uruchomić blok po opóźnieniu, na przykład -performSelector: withObject: afterDelay :?

735

Czy istnieje sposób na wywołanie bloku z prymitywnym parametrem po opóźnieniu, np. Użycie, performSelector:withObject:afterDelay:ale z argumentem takim jak int/ double/ float?

Egil
źródło
To rzadki moment, w którym GCD może zrobić coś, czego NSOperation nie może zrobić, prawda?
Anonimowy Biały

Odpowiedzi:

1174

Myślę, że szukasz dispatch_after(). Wymaga, aby Twój blok nie akceptował żadnych parametrów, ale możesz po prostu pozwolić, aby blok przechwycił te zmienne z twojego zasięgu lokalnego.

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

Więcej: https://developer.apple.com/documentation/dispatch/1452876-dispatch_after

Ryan
źródło
88
W rzeczywistości to nieprawda. Obiekty przechwycone przez blok, które nie są oznaczone jako znajdujące się w pamięci __block, są zatrzymywane przez blok i zwalniane przez blok, gdy zostanie zniszczony (gdy jego liczba zatrzymań spadnie do 0). Oto dokumentacja na ten temat: developer.apple.com/library/mac/documentation/Cocoa/Conceptual/...
Ryan
9
ten dispatch_time(DISPATCH_TIME_NOW, 10ull * NSEC_PER_SEC)fragment jest paskudny. Czy nie ma na to czystszego sposobu?
samvermette,
7
Tak, dispatch_get_current_queue()zawsze zwraca kolejkę, z której uruchamiany jest kod. Kiedy więc ten kod zostanie uruchomiony z głównego wątku, blok zostanie również wykonany w głównym wątku.
Ryan
20
dispatch_get_current_queue()jest przestarzałe
Matej
9
Oprócz NSEC_PER_SEC, istnieje również NSEC_PER_MSEC, na wypadek, gdybyś chciał określić milisekundy;)
cprcrack 24.10.2013
504

Możesz użyć, dispatch_afteraby wywołać blok później. W Xcode zacznij pisać dispatch_afteri naciśnij przycisk Enterautouzupełniania, aby:

wprowadź opis zdjęcia tutaj

Oto przykład z dwoma zmiennoprzecinkowymi jako „argumentami”. Nie musisz polegać na żadnym rodzaju makra, a cel kodu jest dość jasny:

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

Cel 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
źródło
45
Uważaj, aby czas opóźnienia nie był podwójny. Więc po prostu nie próbuj NSEC_PER_SEC * 0,5 przez pół sekundy, to nie zadziała! Musisz zejść do milisekund i użyć NSEC_PER_MSEC * 500. Powinieneś więc zmienić przykładowy kod na: int delayInSeconds = 2, aby pokazać, że ludzie nie mogą używać ułamków NSEC_PER_SEC.
malhal
11
@malhal Właściwie NSEC_PER_SEC * 0.5działałoby tak samo jak NSEC_PER_MSEC * 500. Chociaż słusznie zauważysz, że dispatch_timeoczekuje się 64-bitowej liczby całkowitej, oczekuje się, że wartość będzie wyrażona w nanosekundach. NSEC_PER_SECjest zdefiniowany jako 1000000000ull, a pomnożenie tego przez stałą zmiennoprzecinkową 0.5niejawnie wykonałoby arytmetykę zmiennoprzecinkową, dając 500000000.0, zanim zostanie jawnie rzutowane z powrotem na 64-bitową liczbę całkowitą. Tak więc użycie ułamka jest całkowicie dopuszczalne NSEC_PER_SEC.
Junjie
202

Co powiesz na użycie wbudowanej biblioteki fragmentów kodu Xcode?

wprowadź opis zdjęcia tutaj

Aktualizacja dla Swift:

Wiele głosów zachęciło mnie do zaktualizowania tej odpowiedzi.

Wbudowana biblioteka fragmentów kodu Xcode ma dispatch_aftertylko objective-cjęzyk. Ludzie mogą również tworzyć własne niestandardowe fragmenty kodu Swift.

Napisz to w 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#>
    })

Przeciągnij ten kod i upuść go w obszarze biblioteki fragmentów kodu. wprowadź opis zdjęcia tutaj

Na dole listy fragmentów kodu pojawi się nowy obiekt o nazwie My Code Snippet. Edytuj to dla tytułu. Aby uzyskać sugestię podczas wpisywania Xcode, wypełnij Completion Shortcut.

Aby uzyskać więcej informacji, zobacz CreatingaCustomCodeSnippet .

Zaktualizuj Swift 3

Przeciągnij ten kod i upuść go w obszarze biblioteki fragmentów kodu.

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(<#delayInSeconds#>)) {
    <#code to be executed after a specified delay#>
}
Warif Akhand Rishi
źródło
19
Czy ktoś faktycznie używa tej funkcji w Xcode? Wolę po prostu wpisać go jako wyskakujące okienko z sugestiami i są równie łatwe w użyciu.
Supertecnoboff
6
Do tej pory myślałem, że kopiowanie i wklejanie to najprostszy sposób na kodowanie. Teraz po prostu przeciągam i upuszczam .... hahaha
arshu
58

Rozwijając odpowiedź Jaime Cham stworzyłem kategorię NSObject + Blocks, jak poniżej. Czułem, że te metody lepiej pasują do istniejących performSelector:metod NSObject

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

i użyj tak:

[anyObject performBlock:^{
    [anotherObject doYourThings:stuff];
} afterDelay:0.15];
Oliver Pearmain
źródło
5
delayPowinny być NSTimeInterval(co jest double). #import <UIKit/UIKit.h>nie jest potrzebne. I nie rozumiem, dlaczego - (void)performBlock:(void (^)())block;może być przydatny, więc można go usunąć z nagłówka.
znaczenie-ma znaczenie
@ znaczenie-ważne, oba ważne punkty +1, odpowiednio zaktualizowałem swoją odpowiedź.
Oliver Pearmain,
w ogóle nie jest to poprawne, performSelector musi zostać jawnie usunięty podczas dealloc, w przeciwnym razie napotkasz naprawdę dziwne zachowanie i ulega awarii, bardziej poprawne jest użycie dispatch_after
Peter Lapisu
21

Być może prostsze niż przechodzenie przez GCD, gdzieś w klasie (np. „Util”) lub w kategorii na obiekcie:

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

Więc użyć:

[Util runAfterDelay:2 block:^{
    NSLog(@"two seconds later!");
}];
Jaime Cham
źródło
3
@Jaimie Cham Dlaczego według ciebie przejście przez GCD jest trudne?
Besi
2
Przejście przez GCD ma nieco inne zachowanie niż PerformSelector: afterDelay :, więc mogą istnieć powody, aby nie używać GCD. Zobacz na przykład następujące pytanie: stackoverflow.com/questions/10440412/…
fishinear
Dlaczego kopiujesz blok przed przekazaniem go do performSelector?
c roald 12.12.12
1
Przepraszam za opóźnienie. @ Croald: Myślę, że potrzebujesz kopii, aby przenieść blok ze stosu na stos.
Jaime Cham
@Besi: bardziej pracowity i ukrywa intencję.
Jaime Cham
21

Dla Swift stworzyłem funkcję globalną, nic specjalnego, przy użyciu tej dispatch_aftermetody. Podoba mi się to bardziej, ponieważ jest czytelny i łatwy w użyciu:

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

Którego możesz użyć w następujący sposób:

performBlock({ () -> Void in
    // Perform actions
}, afterDelay: 0.3)
Antoine
źródło
1
Sugeruję zamianę argumentów i zmianę nazwy na after. Następnie możesz napisać:after(2.0){ print("do somthing") }
Lars Blumberg,
16

Oto moje 2 centy = 5 metod;)

Lubię enkapsulować te szczegóły i niech AppCode powie mi, jak dokończyć zdania.

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
źródło
8

PerformSelector: WithObject zawsze pobiera obiekt, więc w celu przekazania argumentów takich jak int / double / float itp ... Możesz użyć czegoś takiego.

// NSNumber jest obiektem ..

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



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

     [txtUsername setAlpha: [number floatValue] ];

}

W ten sam sposób możesz użyć [NSNumber numberWithInt:] itp. .... a w metodzie odbierającej możesz przekonwertować liczbę na swój format jako [liczba int] lub [liczba podwójna].

Augustyn
źródło
8

Funkcja dispatch_after wysyła obiekt bloku do kolejki wysyłki po upływie określonego czasu. Użyj poniższego kodu, aby wykonać niektóre czynności związane z interfejsem użytkownika po 2,0 sekundach.

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

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

          })
Himanshu Mahajan
źródło
jest literówka: mainQueue, zamiastmainQueue)
Bastian
5

Oto Swift 3 sposób na kolejkowanie pracy po opóźnieniu.

DispatchQueue.main.asyncAfter(
  DispatchTime.now() + DispatchTimeInterval.seconds(2)) {
    // do work
}
cpimhoff
źródło
5

Oto przydatny pomocnik, który zapobiega nawracaniu połączeń GCD w kółko:

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

Teraz po prostu opóźniasz kod w głównym wątku w następujący sposób:

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

Jeśli chcesz opóźnić kod do innego wątku :

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

Jeśli wolisz Framework, który ma również kilka przydatnych funkcji, sprawdź HandySwift . Możesz dodać go do swojego projektu za pośrednictwem Kartaginy, a następnie użyć go dokładnie tak, jak w powyższych przykładach:

import HandySwift    

delay(bySeconds: 1.5) { 
    // delayed code
}
Jeehut
źródło
Zakłada to, że twoja funkcja opóźnienia wykonuje kod z wątku w tle. Ktoś korzystający z Twojego przykładu może mieć naprawdę ciężkie czasy debugowania aplikacji ulegającej awarii, jeśli umieści dowolny kod związany z interfejsem użytkownika w // sekcji kodu opóźnionego .
nalexn
Domyślnie moja metoda używa głównego wątku, więc to nie powinno się zdarzyć. Zobacz domyślną wartość dispatchLevel .Main?
Jeehut,
4

W swift 3 możemy po prostu użyć funkcji DispatchQueue.main.asyncAfter, aby uruchomić dowolną funkcję lub akcję po upływie „n” sekund. Tutaj w kodzie ustawiliśmy opóźnienie po 1 sekundzie. Wywołujesz dowolną funkcję wewnątrz ciała tej funkcji, która uruchomi się z opóźnieniem 1 sekundy.

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

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

}
Rouny
źródło
4

Xcode 10.2 i Swift 5 i nowsze wersje

DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
   // code to execute                 
})
midhun p
źródło
1

Możesz albo zawinąć argument we własną klasę, albo zawinąć wywołanie metody w metodę, która nie musi być przekazywana w typie pierwotnym. Następnie wywołaj tę metodę po opóźnieniu i w ramach tej metody wykonaj selektor, który chcesz wykonać.

GendoIkari
źródło
1

Oto jak możesz uruchomić blok po opóźnieniu w 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)
}

Jest zawarty jako standardowa funkcja w moim repozytorium .

Esqarrouth
źródło
1

Swift 3 i Xcode 8.3.2

Ten kod pomoże ci, dodam też wyjaśnienie

// 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
źródło
0

Uważam, że autor nie pyta, jak czekać na ułamek czasu (opóźnienie), ale zamiast tego, jak przekazać skalar jako argument selektora (withObject :), a najszybszym sposobem we współczesnym celu C jest:

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

twój selektor musi zmienić parametr na NSNumber i pobrać wartość za pomocą selektora takiego jak floatValue lub doubleValue

André
źródło