Is there a way to call a block with a primitive parameter after a delay, like using performSelector:withObject:afterDelay:
but with an argument like int
/double
/float
?
问题:
回答1:
I think you\'re looking for dispatch_after()
. It requires your block to accept no parameters, but you can just let the block capture those variables from your local scope instead.
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);
});
More: https://developer.apple.com/documentation/dispatch/1452876-dispatch_after
回答2:
You can use dispatch_after
to call a block later. In Xcode, start typing dispatch_after
and hit Enter
to autocomplete to the following:
Here\'s an example with two floats as \"arguments.\" You don\'t have to rely on any type of macro, and the intent of the code is quite clear:
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);
});
回答3:
How about using Xcode built-in code snippet library?
Update for Swift:
Many up votes inspired me to update this answer.
The build-in Xcode code snippet library has dispatch_after
for only objective-c
language. People can also create their own Custom Code Snippet for Swift
.
Write this in 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#>
})
Drag this code and drop it in the code snippet library area.
Bottom of the code snippet list, there will be a new entity named My Code Snippet
. Edit this for a title. For suggestion as you type in the Xcode fill in the Completion Shortcut
.
For more info see CreatingaCustomCodeSnippet.
Update Swift 3
Drag this code and drop it in the code snippet library area.
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(<#delayInSeconds#>)) {
<#code to be executed after a specified delay#>
}
回答4:
Expanding on Jaime Cham\'s answer I created a NSObject+Blocks category as below. I felt these methods better matched the existing performSelector:
NSObject methods
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
and use like so:
[anyObject performBlock:^{
[anotherObject doYourThings:stuff];
} afterDelay:0.15];
回答5:
Perhaps simpler than going thru GCD, in a class somewhere (e.g. \"Util\"), or a Category on 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];
}
So to use:
[Util runAfterDelay:2 block:^{
NSLog(@\"two seconds later!\");
}];
回答6:
For Swift I\'ve created a global function, nothing special, using the dispatch_after
method. I like this more as it\'s readable and easy to use:
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)
}
Which you can use as followed:
performBlock({ () -> Void in
// Perform actions
}, afterDelay: 0.3)
回答7:
Here are my 2 cents = 5 methods ;)
I like encapsulate these details and have AppCode tell me how to finish my sentences.
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 always takes an object, so in order to pass arguments like int/double/float etc..... You can use something like this.
//NSNumber is an object..
[self performSelector:@selector(setUserAlphaNumber:)
withObject: [NSNumber numberWithFloat: 1.0f]
afterDelay:1.5];
-(void) setUserAlphaNumber: (NSNumber*) number{
[txtUsername setAlpha: [number floatValue] ];
}
Same way you can use [NSNumber numberWithInt:] etc.... and in the receiving method you can convert the number into your format as [number int] or [number double].
回答9:
The dispatch_after function dispatches a block object to a dispatch queue after a given period of time. Use below code to perform some UI related taks after 2.0 seconds.
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\")
})
In 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: {
})
回答10:
Here\'s the Swift 3 way to queue work after a delay.
DispatchQueue.main.asyncAfter(
DispatchTime.now() + DispatchTimeInterval.seconds(2)) {
// do work
}
回答11:
Here\'s a handy helper to prevent making the annoying GCD call over and over again:
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)
}
}
}
Now you simply delay your code on the Main thread like this:
delay(bySeconds: 1.5) {
// delayed code
}
If you want to delay your code to different thread:
delay(bySeconds: 1.5, dispatchLevel: .background) {
// delayed code that will run on background thread
}
If you prefer a Framework that also has some more handy features then checkout HandySwift. You can add it to your project via Carthage then use it exactly like in the examples above:
import HandySwift
delay(bySeconds: 1.5) {
// delayed code
}
回答12:
There\'s a nice one in the BlocksKit framework.
BlocksKit
(and the class)
BBlocksKit.m
回答13:
In swift 3, We can simply use DispatchQueue.main.asyncAfter function to trigger any function or action after the delay of \'n\' seconds. Here in code we have set delay after 1 second. You call any function inside the body of this function which will trigger after the delay of 1 second.
let when = DispatchTime.now() + 1
DispatchQueue.main.asyncAfter(deadline: when) {
// Trigger the function/action after the delay of 1Sec
}
回答14:
You can either wrap the argument in your own class, or wrap the method call in a method that doesn\'t need to be passed in the primitive type. Then call that method after your delay, and within that method perform the selector you wish to perform.
回答15:
Here is how you can trigger a block after a delay in 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)
}
Its included as a standard function in my repo.
回答16:
Swift 3 & Xcode 8.3.2
This code will help you, i add an explanation too
// 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 :)
}
}
}
回答17:
I believe the author is not asking how to wait for a fractional time (delay), but instead how to pass a scalar as argument of the selector (withObject:) and the fastest way in modern objective C is:
[obj performSelector:... withObject:@(0.123123123) afterDelay:10]
your selector have to change its parameter to NSNumber, and retrieve the value using a selector like floatValue or doubleValue