ios - ¿Cómo se activa un bloque después de un retraso, como-performSelector: withObject: afterDelay:?
objective-c grand-central-dispatch (17)
¿Hay una manera de llamar a un bloque con un parámetro primitivo después de un retraso, como usar performSelector:withObject:afterDelay:
pero con un argumento como int
/ double
/ float
?
¿Qué hay de usar la biblioteca de fragmentos de código incorporada de Xcode?
Actualización para Swift:
Muchos votos me inspiraron a actualizar esta respuesta.
La biblioteca incorporada de fragmentos de código de Xcode tiene dispatch_after
solo para el lenguaje objective-c
. Las personas también pueden crear su propio Fragmento de código personalizado para Swift
.
Escribe esto en 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#>
})
Arrastre este código y suéltelo en el área de la biblioteca de fragmentos de código.
En la parte inferior de la lista de fragmentos de código, habrá una nueva entidad llamada My Code Snippet
. Edita esto para un título. Para sugerencias mientras escribe en Xcode, complete el Completion Shortcut
.
Para obtener más información, consulte CreatingaCustomCodeSnippet .
Actualizar Swift 3
Arrastre este código y suéltelo en el área de la biblioteca de fragmentos de código.
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(<#delayInSeconds#>)) {
<#code to be executed after a specified delay#>
}
Ampliando la respuesta de Jaime Cham, creé una categoría NSObject + Blocks como se muestra a continuación. Sentí que estos métodos coincidían mejor con los métodos existentes de 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
y usar como tal:
[anyObject performBlock:^{
[anotherObject doYourThings:stuff];
} afterDelay:0.15];
Aquí es cómo puede desencadenar un bloque después de un retraso en 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)
}
Se incluye como una función estándar en mi repositorio .
Aquí está la forma Swift 3 de hacer cola después de un retraso.
DispatchQueue.main.asyncAfter(
DispatchTime.now() + DispatchTimeInterval.seconds(2)) {
// do work
}
Aquí están mis 2 centavos = 5 métodos;)
Me gusta resumir estos detalles y hacer que AppCode me diga cómo terminar mis oraciones.
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);
}
Aquí hay un ayudante útil para evitar que la molesta llamada GCD una y otra vez:
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)
}
}
}
Ahora simplemente retrasas tu código en el hilo principal de esta manera:
delay(bySeconds: 1.5) {
// delayed code
}
Si quieres retrasar tu código a un hilo diferente :
delay(bySeconds: 1.5, dispatchLevel: .background) {
// delayed code that will run on background thread
}
Si prefieres un Framework que también tenga algunas funciones más útiles, entonces selecciona HandySwift . Puede agregarlo a su proyecto a través de Cartago y luego usarlo exactamente como en los ejemplos anteriores:
import HandySwift
delay(bySeconds: 1.5) {
// delayed code
}
Creo que el autor no está preguntando cómo esperar un tiempo fraccionario (demora), sino cómo pasar un escalar como argumento del selector (conObjeto :) y la forma más rápida en el objetivo C moderno es:
[obj performSelector:... withObject:@(0.123123123) afterDelay:10]
su selector tiene que cambiar su parámetro a NSNumber, y recuperar el valor utilizando un selector como floatValue o doubleValue
Creo que estás buscando dispatch_after()
. Requiere que su bloque no acepte ningún parámetro, pero puede dejar que el bloque capture esas variables desde su alcance local.
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);
});
Más: https://developer.apple.com/documentation/dispatch/1452876-dispatch_after
En swift 3, simplemente podemos usar la función DispatchQueue.main.asyncAfter para activar cualquier función o acción después de la demora de ''n'' segundos. Aquí en el código hemos establecido el retraso después de 1 segundo. Usted llama a cualquier función dentro del cuerpo de esta función que se activará después de un retraso de 1 segundo.
let when = DispatchTime.now() + 1
DispatchQueue.main.asyncAfter(deadline: when) {
// Trigger the function/action after the delay of 1Sec
}
La función dispatch_after despacha un objeto de bloque a una cola de despacho después de un período de tiempo determinado. Utilice el código de abajo para realizar algunos intentos relacionados con la interfaz de usuario después de 2.0 segundos.
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")
})
En 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: {
})
Para Swift, he creado una función global, nada especial, utilizando el método dispatch_after
. Me gusta esto más porque es legible y fácil de usar:
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)
}
Que puedes usar como sigue:
performBlock({ () -> Void in
// Perform actions
}, afterDelay: 0.3)
PerformSelector: WithObject siempre toma un objeto, así que para pasar argumentos como int / double / float, etc. Puedes usar algo como esto.
// NSNumber es un objeto ..
[self performSelector:@selector(setUserAlphaNumber:)
withObject: [NSNumber numberWithFloat: 1.0f]
afterDelay:1.5];
-(void) setUserAlphaNumber: (NSNumber*) number{
[txtUsername setAlpha: [number floatValue] ];
}
De la misma manera puede usar [NSNumber numberWithInt:] etc .... y en el método de recepción, puede convertir el número a su formato como [number int] o [number double].
Puede envolver el argumento en su propia clase o envolver la llamada de método en un método que no necesite pasar en el tipo primitivo. Luego, llame a ese método después de su demora y, dentro de ese método, realice el selector que desea realizar.
Puedes usar dispatch_after
para llamar a un bloque más tarde. En Xcode, comience a escribir dispatch_after
y presione Enter
para completar automáticamente lo siguiente:
Aquí hay un ejemplo con dos flotantes como "argumentos". No tiene que confiar en ningún tipo de macro, y la intención del código es bastante clara:
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)")
}
C objetivo
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);
});
Tal vez más simple que ir a través de GCD, en una clase en algún lugar (por ejemplo, "Util"), o una Categoría en Objeto:
+ (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];
}
Para usar:
[Util runAfterDelay:2 block:^{
NSLog(@"two seconds later!");
}];
Swift 3 y Xcode 8.3.2
Este código te ayudará, también añado una explicación.
// 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 :)
}
}
}