如何在延迟后触发一个块,类似于-performSelector:withObject:afterDelay:?

774
有没有一种方法可以在延迟后使用一个原始参数调用块,就像使用 performSelector:withObject:afterDelay: 一样,但参数是像 int/double/float 这样的类型?
20个回答

1226

我认为你需要使用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)时由块释放。这是相关文档链接:http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW3 - Ryan
10
这个 dispatch_time(DISPATCH_TIME_NOW, 10ull * NSEC_PER_SEC) 的片段很糟糕。难道没有更简洁的方式吗? - samvermette
8
是的,dispatch_get_current_queue()总是返回正在运行代码的队列。因此,当该代码在主线程上运行时,这个块也将在主线程上执行。 - Ryan
20
dispatch_get_current_queue()现已被弃用。 - Matej
10
除了NSEC_PER_SEC之外,还有NSEC_PER_MSEC存在,以防您想要指定毫秒。 - cprcrack
显示剩余17条评论

533

你可以使用dispatch_after延迟调用一个block。在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)")
}

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

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

49
请注意,延迟时间不是双倍。因此,不要尝试使用 NSEC_PER_SEC * 0.5 来表示半秒钟,这样行不通!你需要将其转换为毫秒,并使用 NSEC_PER_MSEC * 500。因此,你应该将代码示例更改为:int delayInSeconds = 2,以向人们展示不能使用 NSEC_PER_SEC 的分数。 - malhal
16
@malhal, 实际上,NSEC_PER_SEC * 0.5NSEC_PER_MSEC * 500 的效果是一样的。虽然您正确地指出了dispatch_time需要一个64位整数,但它所需的值是以纳秒为单位的。NSEC_PER_SEC 被定义为 1000000000ull,将其乘以浮点常量 0.5 将隐式执行浮点运算,得到 500000000.0,然后再明确地转换回一个64位整数。因此,使用 NSEC_PER_SEC 的一部分是完全可以接受的。 - junjie
此答案垃圾,因为我无法复制粘贴它。 - CDM social medias in bio

205

如何使用Xcode内置的代码片段库?

enter image description here

适用于Swift:

许多赞鼓舞了我更新这个答案。

Xcode内置的代码片段库只有针对Objective-C语言的dispatch_after。人们也可以为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以进行建议。

有关更多信息,请参见CreatingaCustomCodeSnippet

更新Swift 3

将此代码拖动并放入代码段库区域。

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

19
有人实际在 Xcode 中使用这个功能吗?我更喜欢只是在代码提示弹出时键入,它们同样易于使用。 - Supertecnoboff
6
直到现在,我一直认为复制和粘贴是编码最简单的方式。现在我只需要拖放......哈哈哈 - arshu

59

根据Jaime Cham的答案,我创建了一个NSObject+Blocks类别,代码如下。我认为这些方法更加匹配现有的NSObject的performSelector:方法。

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
延迟应该使用NSTimeInterval(即double)。不需要导入<UIKit/UIKit.h>。而且,我不认为- (void)performBlock:(void (^)())block;有用,因此可以从头文件中删除。 - meaning-matters
@meaning-matters,两个观点都很有道理,+1。我已经相应地更新了我的答案。 - Oliver Pearmain
这完全不正确,performSelector必须在dealloc上显式删除,否则您将遇到非常奇怪的行为和崩溃,更正确的方法是使用dispatch_after。 - Peter Lapisu

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
为什么你认为通过GCD很困难? - Besi
2
通过GCD进行操作与使用PerformSelector:afterDelay:稍有不同,因此可能有不使用GCD的原因。例如,请参见以下问题:https://dev59.com/YGkv5IYBdhLWcg3wqSj1 - fishinear
为什么在传递给performSelector之前要复制该块? - c roald
1
抱歉耽搁了。 @croald:我认为你需要复制将块从堆栈移动到堆中。 - Jaime Cham
@Besi:更啰嗦且隐藏意图。 - Jaime Cham
显示剩余2条评论

21

对于Swift,我创建了一个全局函数,没有什么特别的,使用了dispatch_after方法。我更喜欢这种方式,因为它更易读和使用:

对于 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

16

这是我的个人意见 = 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);
}

9

Xcode 10.2及以上版本和Swift 5

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

ObjC版本

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    //code to execute
});

1
OP在问题中标记了ObjC,并询问了PerformSelector:AfterDelay:等ObjC选择器的替代方法。 - Motti Shneor

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

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

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]。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接