ChatGPT解决这个技术问题 Extra ChatGPT

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

有没有办法在延迟后调用带有原始参数的块,例如使用 performSelector:withObject:afterDelay: 但使用 int/double/float 之类的参数?


C
Cœur

我认为您正在寻找 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


事实上,这不是真的。未被标记为在 __block 存储中的块捕获的对象由块保留,并在块被销毁时被块释放(当其保留计数变为 0 时)。以下是相关文档:developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…
这个 dispatch_time(DISPATCH_TIME_NOW, 10ull * NSEC_PER_SEC) 片段很讨厌。没有更清洁的方法吗?
是的,dispatch_get_current_queue() 始终返回运行代码的队列。因此,当这段代码从主线程运行时,该块也将在主线程上执行。
dispatch_get_current_queue() 现已弃用
除了 NSEC_PER_SEC 之外,NSEC_PER_MSEC 也确实存在,以防您想指定毫秒;)
f
farzadshbfn

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

https://i.stack.imgur.com/lO1Kc.png

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

斯威夫特 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);
});

注意延迟时间不是两倍。所以只是不要尝试 NSEC_PER_SEC * 0.5 半秒钟它不会工作!您需要降低到毫秒并使用 NSEC_PER_MSEC * 500。因此您应该将代码示例更改为:int delayInSeconds = 2 以表明人们不能使用 NSEC_PER_SEC 的分数。
@malhal 实际上,NSEC_PER_SEC * 0.5NSEC_PER_MSEC * 500 的工作方式相同。虽然您正确地注意到 dispatch_time 需要一个 64 位整数,但它需要的值是以纳秒为单位。 NSEC_PER_SEC 定义为 1000000000ull,将其与浮点常量 0.5 相乘将隐式执行浮点运算,产生 500000000.0,然后再显式转换回 64 位整数。所以使用 NSEC_PER_SEC 的一小部分是完全可以接受的。
这节省了一天。谢谢。
C
Community

使用 Xcode 内置的代码片段库怎么样?

https://i.stack.imgur.com/bXYBo.jpg

斯威夫特更新:

许多赞成票激励我更新这个答案。

内置 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#>
    })

https://i.stack.imgur.com/ym3Kw.png

在代码片段列表的底部,将有一个名为 My Code Snippet 的新实体。编辑此标题。对于您在 Xcode 中键入时的建议,请填写 Completion Shortcut

有关详细信息,请参阅 CreatingaCustomCodeSnippet

更新斯威夫特 3

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

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

有人真的在 Xcode 中使用过这个功能吗?我更喜欢将其作为代码建议弹出窗口输入,并且同样易于使用。
直到知道我只是认为复制和粘贴是最简单的编码方式。现在我只是拖放....哈哈哈
O
Oliver Pearmain

扩展 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];

delay 应属于 NSTimeInterval(即 double)。 #import <UIKit/UIKit.h> 不是必需的。而且,我不明白为什么 - (void)performBlock:(void (^)())block; 可能有用,因此可以从标题中删除。
@meaning-matters,两个有效点+1,我已经相应地更新了我的答案。
这根本不正确,必须在 dealloc 上显式删除 performSelector,否则您将遇到非常奇怪的行为和崩溃,更正确的是使用 dispatch_after
J
Jaime Cham

也许比通过 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!");
}];

@Jaimie Cham 为什么你认为通过 GCD 很困难?
通过 GCD 的行为确实与 PerformSelector:afterDelay: 略有不同,因此可能有理由不使用 GCD。例如,请参阅以下问题:stackoverflow.com/questions/10440412/…
为什么在将块传递给 performSelector 之前复制它?
抱歉耽搁了。 @croald:我认为您需要副本才能将块从堆栈移动到堆。
@Besi:更多罗嗦并隐藏意图。
A
Antoine

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

我建议交换参数并将其重命名为 after。然后你可以写:after(2.0){ print("do somthing") }
D
Dan Rosenstark

这是我的 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);
}

A
Augustine

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


H
Himanshu Mahajan

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)
m
midhun p

Xcode 10.2 和 Swift 5 及更高版本

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

OP 用 ObjC 标记了这个问题,并要求替代 ObjC 选择器 PerformSelector:AfterDelay: 等。
c
cpimhoff

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

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

J
Jeehut

这是一个方便的助手,可以防止一遍又一遍地进行烦人的 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
}

如果您更喜欢具有一些更方便功能的框架,请查看HandySwift。您可以通过 Carthage 将它添加到您的项目中,然后像上面的示例一样使用它:

import HandySwift    

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

这暗示您的延迟函数从后台线程执行代码。使用您的示例的人可能很难调试崩溃的应用程序,如果他们将任何与 UI 相关的代码放入 // 延迟代码部分。
默认情况下,我的方法使用主线程,因此不应该发生这种情况。看到默认为 .Main 的 dispatchLevel 了吗?
N
NANNAV

BlocksKit 框架中有一个很好的框架。

BlocksKit

(和班级)

BBlocksKit.m


P
Pang

在 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

}

o
oskarko

按 Cmd + Shift + L 显示 Xcode 内置代码片段库:

https://i.stack.imgur.com/DYsID.png

之后寻找调度,然后,只需拖放到您的代码中。


a
auspicious99

https://i.stack.imgur.com/2UebJ.png

(2020 年 6 月在 Xcode 11.3.1 上验证)

Xcode 提供了一个代码片段来执行此操作。您只需输入延迟值和您希望在延迟后运行的代码。

单击 Xcode 右上角的 + 按钮,同时编辑一些代码(而不是在 Project Navigator 中,其中将显示其他库,如 Capabilities)确保 Snippets 库(见截图,图标{ }) 在可用图标中被选中。搜索之后它只会返回 1 个搜索结果,这是所需的片段(见截图)。双击它,你就可以开始了。

https://i.stack.imgur.com/zJ0uv.png


G
GendoIkari

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


J
Jagat Dave

以下是在 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)
}

它作为标准函数包含在 my repo 中。


A
Andrian Rahardja

斯威夫特 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 :)
        }
    }   
}

T
The Horny Coder

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

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

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