Как вы запускаете блок после задержки, например -performSelector: withObject: afterDelay :?

734

Есть ли способ вызвать блок с параметром примитива после задержки, например, performSelector:withObject:afterDelay:с использованием аргумента типа int/ double/ float?

Эгиль
источник
Это редкий момент, когда GCD может сделать что-то, чего не может NSOperation, не так ли?
Аноним Уайт

Ответы:

1175

Я думаю, что вы ищете dispatch_after(). Это требует, чтобы ваш блок не принимал никаких параметров, но вы можете просто позволить блоку захватывать эти переменные из локальной области видимости.

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

Подробнее: https://developer.apple.com/documentation/dispatch/1452876-dispatch_after

Райан
источник
88
На самом деле это не так. Объекты, захваченные блоком, которые не помечены как находящиеся в хранилище __block, сохраняются блоком и освобождаются блоком, когда он уничтожается (когда его счетчик хранения становится равным 0). Вот документация по этому вопросу: developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…
Райан
9
этот dispatch_time(DISPATCH_TIME_NOW, 10ull * NSEC_PER_SEC)фрагмент неприятен. Нет ли более чистого пути для этого?
Самверметт
7
Да, dispatch_get_current_queue()всегда возвращает очередь, из которой выполняется код. Поэтому, когда этот код запускается из основного потока, блок также будет выполняться в основном потоке.
Райан
20
dispatch_get_current_queue()устарела сейчас
Matej
9
Помимо NSEC_PER_SEC, NSEC_PER_MSEC также существует, если вы хотите указать миллисекунды;)
cprcrack
504

Вы можете использовать dispatch_afterдля вызова блока позже. В XCode начните печатать dispatch_afterи нажмите, Enterчтобы автозаполнить к следующему:

введите описание изображения здесь

Вот пример с двумя числами в качестве аргументов. Вам не нужно полагаться на какой-либо тип макроса, и цель кода совершенно ясна:

Свифт 3, Свифт 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)")
}

Цель С

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
Будьте осторожны, время задержки не удваивается. Так что просто не пытайтесь NSEC_PER_SEC * 0.5 в течение полсекунды, это не будет работать! Вам нужно перейти на миллисекунды и использовать NSEC_PER_MSEC * 500. Поэтому вы должны изменить пример кода на: int delayInSeconds = 2, чтобы показать, что люди не могут использовать дробные части NSEC_PER_SEC.
Малхал
11
@malhal На самом деле, NSEC_PER_SEC * 0.5будет работать так же, как NSEC_PER_MSEC * 500. Хотя вы правильно заметили, что dispatch_timeожидает 64-разрядное целое число, ожидаемое значение составляет наносекунды. NSEC_PER_SECопределяется как 1000000000ull, и умножение этого на константу с плавающей точкой 0.5будет неявно выполнять арифметику с плавающей точкой, давая 500000000.0, прежде чем она будет явно приведена обратно к 64-битному целому числу. Так что вполне приемлемо использовать долю NSEC_PER_SEC.
Junjie
202

Как насчет использования встроенной библиотеки фрагментов кода Xcode?

введите описание изображения здесь

Обновление для Swift:

Много голосов подтолкнуло меня к обновлению этого ответа.

Встроенная библиотека фрагментов кода Xcode имеет dispatch_afterтолько objective-cязык. Люди могут также создать свой собственный фрагмент кода дляSwift .

Напишите это в 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#>
    })

Перетащите этот код и поместите его в область библиотеки фрагментов кода. введите описание изображения здесь

Внизу списка фрагментов кода появится новая сущность с именем My Code Snippet. Отредактируйте это для заголовка. Для предложения при вводе в Xcode заполнитеCompletion Shortcut .

Для получения дополнительной информации см. СозданиеaCustomCodeSnippet .

Обновление Swift 3

Перетащите этот код и поместите его в область библиотеки фрагментов кода.

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(<#delayInSeconds#>)) {
    <#code to be executed after a specified delay#>
}
Warif Akhand Rishi
источник
19
Кто-нибудь на самом деле использует эту функцию в XCode? Я предпочитаю просто вводить его, как всплывающее окно с подсказками кода, и оно так же просто в использовании.
Supertecnoboff
6
До тех пор, пока я не узнал, я просто думал, что копировать и вставить - это самый простой способ написания кода. Теперь я просто перетаскиваю .... хахаха
аршу
58

Расширяя ответ Хайме Чама, я создал категорию NSObject + Blocks, как показано ниже. Я чувствовал, что эти методы лучше соответствуют существующимperformSelector: методам 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

и использовать так:

[anyObject performBlock:^{
    [anotherObject doYourThings:stuff];
} afterDelay:0.15];
Оливер Пирмейн
источник
5
delayДолжно быть NSTimeInterval(что является double). #import <UIKit/UIKit.h>не нужен И я не понимаю, почему это - (void)performBlock:(void (^)())block;может быть полезно, поэтому можно удалить из заголовка.
значение имеет значение
@ значения-вопросы, оба действительных пункта +1, я обновил свой ответ соответственно.
Оливер Пирмен
это совсем не правильно, executeSelector должен быть явно удален на dealloc, иначе вы столкнетесь с действительно странным поведением и сбоями, более правильным является использование dispatch_after
Peter Lapisu
21

Возможно, проще, чем пройти через GCD, где-нибудь в классе (например, «Util») или в Category on Object:

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

Итак, использовать:

[Util runAfterDelay:2 block:^{
    NSLog(@"two seconds later!");
}];
Хайме Чам
источник
3
@Jaimie Cham Почему вы думаете, что пройти через GCD сложно?
Беси
2
Пройдя через GCD, поведение немного отличается от PerformSelector: afterDelay :, поэтому могут быть причины не использовать GCD. См., Например, следующий вопрос: stackoverflow.com/questions/10440412/…
fishinear
Почему вы копируете блок перед передачей его в executeSelector?
c roald 12.12.12
1
Извините за задержку. @croald: я думаю, что вам нужна копия, чтобы переместить блок из стека в кучу.
Хайме Чам
@Besi: более многословно и скрывает намерения.
Хайме Чам
21

Для Swift я создал глобальную функцию, ничего особенного, используя dispatch_afterметод. Мне это нравится больше, так как он читается и прост в использовании:

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

Который вы можете использовать следующим образом:

performBlock({ () -> Void in
    // Perform actions
}, afterDelay: 0.3)
Antoine
источник
1
Я предлагаю поменять аргументы и переименовать его в after. Тогда вы можете написать:after(2.0){ print("do somthing") }
Ларс Блумберг
16

Вот мои 2 цента = 5 методов;)

Мне нравится инкапсулировать эти детали, и AppCode говорит мне, как закончить мои предложения.

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 всегда принимает объект, поэтому для передачи таких аргументов, как int / double / float и т. Д. ..... Вы можете использовать что-то вроде этого.

// NSNumber - это объект ..

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



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

     [txtUsername setAlpha: [number floatValue] ];

}

Таким же образом вы можете использовать [NSNumber numberWithInt:] и т. Д .... и в методе получения вы можете преобразовать число в свой формат как [число int] или [число двойное].

Augustine
источник
8

Функция dispatch_after отправляет объект блока в очередь отправки по истечении заданного периода времени. Используйте приведенный ниже код для выполнения некоторых связанных с пользовательским интерфейсом тактов через 2,0 секунды.

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

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

          })
Химаншу Махаджан
источник
есть опечатка: mainQueue, вместоmainQueue)
Бастиан
5

Вот Swift 3 способ поставить работу в очередь после задержки.

DispatchQueue.main.asyncAfter(
  DispatchTime.now() + DispatchTimeInterval.seconds(2)) {
    // do work
}
cpimhoff
источник
5

Вот полезный помощник для предотвращения повторного многократного вызова 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)
        }
    }
}

Теперь вы просто задерживаете свой код в главном потоке следующим образом:

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

Если вы хотите отложить ваш код в другой поток :

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

Если вы предпочитаете Framework, в котором также есть несколько удобных функций, обратитесь к HandySwift . Вы можете добавить его в свой проект через Carthage, а затем использовать его точно так же, как в примерах выше:

import HandySwift    

delay(bySeconds: 1.5) { 
    // delayed code
}
Jeehut
источник
Это подразумевает, что ваша функция задержки выполняет код из фонового потока. Кому-то, использующему ваш пример, может быть очень тяжело отлаживать сбой приложения, если он помещает какой-либо связанный с пользовательским интерфейсом код в секцию отложенного кода .
Nalexn
По умолчанию мой метод использует основной поток, чтобы этого не произошло. Смотрите dispatchLevel по умолчанию .Main?
Jeehut
4

В быстром 3, мы можем просто использовать функцию DispatchQueue.main.asyncAfter для запуска любой функции или действия после задержки «n» секунд. Здесь в коде мы установили задержку через 1 секунду. Вы вызываете любую функцию внутри тела этой функции, которая сработает после задержки в 1 секунду.

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

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

}
Rouny
источник
4

Xcode 10.2 и Swift 5 и выше

DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
   // code to execute                 
})
мидхун р
источник
1

Вы можете либо обернуть аргумент в своем собственном классе, либо обернуть вызов метода в метод, который не нужно передавать в примитивном типе. Затем вызовите этот метод после вашей задержки, и в пределах этого метода выполните селектор, который вы хотите выполнить.

Гэндо Икари
источник
1

Вот как вы можете запустить блок после задержки в 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)
}

Он включен в качестве стандартной функции в моем репо .

Esqarrouth
источник
1

Swift 3 & Xcode 8.3.2

Этот код поможет вам, я тоже добавлю объяснение

// 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

Я полагаю, что автор не спрашивает, как ждать дробное время (задержка), но вместо этого, как передать скаляр в качестве аргумента селектора (withObject :), и самый быстрый путь в современной цели C:

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

ваш селектор должен изменить свой параметр на NSNumber и получить значение с помощью селектора, такого как floatValue или doubleValue

Андре
источник