Як ви запускаєте блок після затримки, наприклад -performSelector: withObject: afterDelay :?


735

Чи є спосіб викликати блок із примітивним параметром після затримки, як-от використання, performSelector:withObject:afterDelay:але з аргументом типу int/ double/ float?


Це рідкісний момент, коли GCD може зробити щось NSOperation, чи не так?
Анонімний Білий

Відповіді:


1174

Я думаю, ти шукаєш 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/…
Ryan

9
цей dispatch_time(DISPATCH_TIME_NOW, 10ull * NSEC_PER_SEC)фрагмент неприємний. Немає чистішого способу для цього?
samvermette

7
Так, dispatch_get_current_queue()завжди повертає чергу, з якої запускається код. Отже, коли цей код запускається з основного потоку, блок також буде виконуватися на основному потоці.
Райан

20
dispatch_get_current_queue()зараз застаріло
Матей

9
Крім NSEC_PER_SEC, існує також NSEC_PER_MSEC, якщо ви хочете вказати мілісекунди;)
cprcrack

504

dispatch_afterПізніше ви можете зателефонувати на блок. У Xcode почніть вводити текст dispatch_afterі натисніть Enterна автозаповнення до наступного:

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

Ось приклад з двома поплавками як "аргументи". Вам не потрібно покладатися на будь-який тип макросу, і наміри коду цілком зрозумілі:

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

Швидкий 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)")
}

Мета 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
Будьте уважні, час затримки не подвійний. Тому просто не намагайтеся 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.
1717 року

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.

Для отримання додаткової інформації див. CreateingaCustomCodeSnippet .

Оновлення Swift 3

Перетягніть цей код і опустіть його в область бібліотеки фрагментів коду.

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

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 + Блоки.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, я відповідно оновив свою відповідь.
Олівер Пірмен

це зовсім не правильно, PerformSelector має бути видалено явно на dealloc, інакше ви зіткнетесь із справді дивною поведінкою та збоями, правильніше - використовувати dispatch_after
Peter Lapisu

21

Можливо, простіше, ніж переходити через GCD, в клас десь (наприклад, "Util") або категорію на об'єкт:

+ (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

Чому ви копіюєте блок перед тим, як передавати його виконанню Selector?
c roald

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)

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] або [число подвійне].


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

У швидкому 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
}

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
}

Це неявно, що ваша функція затримки виконує код з фонового потоку. Хтось, хто використовує ваш приклад, може мати справді важкі моменти налагодження програми, яка виходить з ладу, якщо він помістить будь-який код, пов’язаний з користувальницьким інтерфейсом, // розділ із затримкою коду .
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

}

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

Його включено як стандартну функцію до мого репо .


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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.