延迟后如何触发一个块,例如-performSelector:withObject:afterDelay :?


735

有没有一种方法可以在延迟后使用原始参数来调用块,例如使用,performSelector:withObject:afterDelay:但是使用类似int/ double/ 的参数float


这是GCD可以执行NSOperation不能执行的操作的难得点,不是吗?
2012年

Answers:


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/...
瑞恩

9
这个dispatch_time(DISPATCH_TIME_NOW, 10ull * NSEC_PER_SEC)片段很讨厌。没有更清洁的方法吗?
samvermette

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

迅捷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的分数。
malhal 2013年

11
@malhal实际上,其NSEC_PER_SEC * 0.5工作原理与相同NSEC_PER_MSEC * 500。尽管您正确地注意到dispatch_time期望的是64位整数,但是期望的值是十亿分之一秒。NSEC_PER_SEC定义为1000000000ull,然后将其与浮点常量相乘0.5将隐式执行浮点算术yield 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

有关更多信息,请参见CreationaCustomCodeSnippet

更新Swift 3

将此代码拖放到代码段库区域中。

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

19
有人实际上在Xcode中使用此功能吗?我更喜欢在代码建议弹出窗口中键入它,并且易于使用。
Supertecnoboff

6
直到知道我只是认为复制和粘贴是最简单的编码方法。现在我只是拖放...。哈哈哈
arshu

58

扩展Jaime Cham的答案,我创建了一个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,我已经相应更新了答案。
奥利弗·皮尔曼

这根本不正确,必须在dealloc上显式删除performSelector,否则您将遇到真正奇怪的行为并崩溃,更正确的方法是使用dispatch_after
Peter Lapisu 2015年

21

也许比通过GCD,在某个地方的某个类(例如,“ Util”)或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很难?
Besi

2
通过GCD进行操作的行为与PerformSelector:afterDelay:略有不同,因此可能有理由不使用GCD。见,例如,以下问题:stackoverflow.com/questions/10440412/...
fishinear

为什么在将块传递给performSelector之前复制它?
c roald

1
抱歉耽搁了。@croald:我认为您需要将块从堆栈移到堆的副本。
詹姆·占姆

@Besi:比较罗y,隐藏了意图。
詹姆·湛

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") }
Lars Blumberg 2015年

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:]等相同的方法。在接收方法中,您可以将数字转换为[number int]或[number double]格式。


8

在给定的时间段后,dispatch_after函数将块对象调度到调度队列。使用以下代码在2.0秒后执行一些与UI相关的任务。

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

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

现在,您只需像下面这样在Main线程上延迟代码

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

如果要延迟代码到其他线程

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

如果您希望框架也具有更多方便的功能,请签出HandySwift。您可以通过迦太基将其添加到您的项目中然后像上面的示例一样使用它:

import HandySwift    

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

这暗含您的延迟函数从后台线程执行代码。如果将示例中的任何与UI相关的代码放在//延迟代码部分中,则使用您的示例的人可能很难调试正在崩溃的应用程序。
nalexn

默认情况下,我的方法使用主线程,因此不应发生这种情况。看到默认为.Main的dispatchLevel?
Jeehut's


4

在Swift 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之类的选择器来检索值

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.