关于ios:如何在延迟后触发一个块,比如-performSelector:withObject:afterDelay:?

How do you trigger a block after a delay, like -performSelector:withObject:afterDelay:?

有没有办法在延迟后调用带有原始参数的块,比如使用performSelector:withObject:afterDelay:但是使用像int / double / float这样的参数?


我想你正在寻找dispatch_after()。它要求您的块不接受任何参数,但您可以让块从本地范围捕获这些变量。

1
2
3
4
5
6
7
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


您可以稍后使用dispatch_after调用块。在Xcode中,开始键入dispatch_after并点击Enter以自动完成以下内容:

enter image description here

这是一个以两个浮点数作为"参数"的示例。您不必依赖任何类型的宏,并且代码的意图非常明确:

Swift 3,Swift 4

1
2
3
4
5
6
7
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

1
2
3
4
5
6
7
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.

1
2
3
4
5
6
7
8
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);
});


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

enter image description here

Swift更新:

很多投票激励我更新这个答案。

内置的Xcode代码段库只有dispatch_after语言的dispatch_after。人们还可以为Swift创建自己的自定义代码段。

在Xcode中写下这个。

1
2
3
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#>
    })

拖动此代码并将其放在代码段库区域中。
enter image description here

在代码片段列表的底部,会有一个名为My Code Snippet的新实体。编辑此标题。当您在Xcode中输入Completion Shortcut时输入建议。

有关详细信息,请参阅CreatingaCustomCodeSnippet。

更新Swift 3

拖动此代码并将其放在代码段库区域中。

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


扩展了Jaime Cham的答案,我创建了一个NSObject + Blocks类别,如下所示。我觉得这些方法更符合现有的performSelector: NSObject方法

NSObject的+ Blocks.h

1
2
3
4
5
6
7
#import <Foundation/Foundation.h>

@interface NSObject (Blocks)

- (void)performBlock:(void (^)())block afterDelay:(NSTimeInterval)delay;

@end

NSObject的+ Blocks.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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

并使用如下:

1
2
3
[anyObject performBlock:^{
    [anotherObject doYourThings:stuff];
} afterDelay:0.15];


也许比通过GCD,某个类(例如"Util")或者对象类别更简单:

1
2
3
4
5
6
7
8
9
+ (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];
}

所以使用:

1
2
3
[Util runAfterDelay:2 block:^{
    NSLog(@"two seconds later!");
}];


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

1
2
3
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)
}

您可以使用以下内容:

1
2
3
performBlock({ () -> Void in
    // Perform actions
}, afterDelay: 0.3)


这是我的2美分= 5种方法;)

我喜欢封装这些细节,并让AppCode告诉我如何完成我的句子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);
}


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

1
2
3
4
5
6
7
8
            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中:

1
2
3
4
            let dispatchTime: DispatchTime = DispatchTime.now() + Double(Int64(2.0 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
            DispatchQueue.main.asyncAfter(deadline: dispatchTime, execute: {

          })


PerformSelector:WithObject总是接受一个对象,所以为了传递像int / double / float等参数.....你可以使用这样的东西。

// NSNumber是一个对象..

1
2
3
4
5
6
7
8
9
10
11
[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]。


这是一个方便的助手,可以防止反复拨打恼人的GCD电话:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)
        }
    }
}

现在,您只需将代码延迟到主线程上,如下所示:

1
2
3
delay(bySeconds: 1.5) {
    // delayed code
}

如果您想将代码延迟到不同的线程:

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

如果你更喜欢一个也有一些更方便功能的框架,那么请查看HandySwift。您可以通过Carthage将其添加到项目中,然后使用它与上面的示例完全相同:

1
2
3
4
5
import HandySwift    

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


这是Swift 3在延迟后排队工作的方法。

1
2
3
4
DispatchQueue.main.asyncAfter(
  DispatchTime.now() + DispatchTimeInterval.seconds(2)) {
    // do work
}


BlocksKit框架中有一个很好的。

BlocksKit

(和班级)

BBlocksKit.m


在swift 3中,我们可以简单地使用DispatchQueue.main.asyncAfter函数在'n'秒延迟后触发任何函数或动作。 在代码中,我们在1秒后设置了延迟。 您可以调用此函数体内的任何函数,该函数将在延迟1秒后触发。

1
2
3
4
5
6
let when = DispatchTime.now() + 1
DispatchQueue.main.asyncAfter(deadline: when) {

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

}

以下是在Swift延迟后触发块的方法:

1
2
3
4
5
6
7
8
9
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)
}

它包含在我的回购中作为标准功能。


您可以将参数包装在您自己的类中,也可以将方法调用包装在不需要在基本类型中传递的方法中。然后在延迟后调用该方法,并在该方法中执行您希望执行的选择器。


Swift 3和Xcode 8.3.2

这段代码对你有所帮助,我也加了解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 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 :)
        }
    }  
}


Xcode 10.2和Swift 5及以上版本

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

我相信作者不会问如何等待一个小数时间(延迟),而是如何传递一个标量作为选择器的参数(withObject :)和现代目标C中最快的方法是:

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

您的选择器必须将其参数更改为NSNumber,并使用floatValue或doubleValue等选择器检索该值