Esperando hasta que se ejecuten dos bloques asíncronos antes de comenzar otro bloque


192

Cuando usamos GCD, queremos esperar hasta que se ejecuten y realicen dos bloques asíncronos antes de pasar a los siguientes pasos de ejecución. ¿Cuál es la mejor manera de hacer eso?

Intentamos lo siguiente, pero no parece funcionar:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block1
});


dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block2
});

// wait until both the block1 and block2 are done before start block3
// how to do that?

dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block3
});

Vea mi respuesta para Swift 5 que ofrece hasta seis formas diferentes de resolver su problema.
Imanou Petit

Respuestas:


301

Use grupos de despacho: consulte aquí para ver un ejemplo, "Esperando grupos de tareas en cola" en el capítulo "Colas de despacho" de la Guía de programación de concurrencia de la Biblioteca de desarrolladores iOS de Apple

Su ejemplo podría verse así:

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block1
    NSLog(@"Block1");
    [NSThread sleepForTimeInterval:5.0];
    NSLog(@"Block1 End");
});


dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block2
    NSLog(@"Block2");
    [NSThread sleepForTimeInterval:8.0];
    NSLog(@"Block2 End");
});

dispatch_group_notify(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block3
    NSLog(@"Block3");
});

// only for non-ARC projects, handled automatically in ARC-enabled projects.
dispatch_release(group);

y podría producir resultados como este:

2012-08-11 16:10:18.049 Dispatch[11858:1e03] Block1
2012-08-11 16:10:18.052 Dispatch[11858:1d03] Block2
2012-08-11 16:10:23.051 Dispatch[11858:1e03] Block1 End
2012-08-11 16:10:26.053 Dispatch[11858:1d03] Block2 End
2012-08-11 16:10:26.054 Dispatch[11858:1d03] Block3

3
Frio. ¿Las tareas / bloques asíncronos, una vez asociados con el grupo, se ejecutarán de forma secuencial o concurrente? Quiero decir, supongamos que el bloque1 y el bloque2 están asociados con un grupo ahora, ¿el bloque2 esperará hasta que el bloque1 se complete antes de que pueda comenzar a ejecutarse?
Tom

9
Eso depende de usted. dispatch_group_asynces igual que dispatch_asynccon un parámetro de grupo agregado. Entonces, si usa diferentes colas para el bloque1 y el bloque2 o las programa en la misma cola concurrente, pueden ejecutarse simultáneamente; si los programa en la misma cola en serie, se ejecutarán en serie. No es diferente de programar los bloques sin grupos.
Jörn Eyrich

1
¿Esto también se aplica a la ejecución de la publicación del servicio web?
SleepNot

¿Te das cuenta de que el tiempo no es igual al tiempo de suspensión establecido en tu bloque? ¿Por qué sería así?
Damon Yuan

2
En ARC simplemente elimine dispatch_release (group);
loretoparisi

272

Ampliando la respuesta de Jörn Eyrich (vota su respuesta si votaste esta), si no tienes control sobre las dispatch_asyncllamadas de tus bloques, como podría ser el caso de los bloques de finalización asíncrona, puedes usar los grupos GCD usando dispatch_group_enterydispatch_group_leave directamente.

En este ejemplo, pretendemos que computeInBackgroundes algo que no podemos cambiar (imagine que es una devolución de llamada delegada, NSURLConnection completeHandler, o lo que sea), y por lo tanto no tenemos acceso a las llamadas de despacho.

// create a group
dispatch_group_t group = dispatch_group_create();

// pair a dispatch_group_enter for each dispatch_group_leave
dispatch_group_enter(group);     // pair 1 enter
[self computeInBackground:1 completion:^{
    NSLog(@"1 done");
    dispatch_group_leave(group); // pair 1 leave
}];

// again... (and again...)
dispatch_group_enter(group);     // pair 2 enter
[self computeInBackground:2 completion:^{
    NSLog(@"2 done");
    dispatch_group_leave(group); // pair 2 leave
}];

// Next, setup the code to execute after all the paired enter/leave calls.
//
// Option 1: Get a notification on a block that will be scheduled on the specified queue:
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    NSLog(@"finally!");
});

// Option 2: Block an wait for the calls to complete in code already running
// (as cbartel points out, be careful with running this on the main/UI queue!):
//
// dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // blocks current thread
// NSLog(@"finally!");

En este ejemplo, computeInBackground: complete: se implementa como:

- (void)computeInBackground:(int)no completion:(void (^)(void))block {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@"%d starting", no);
        sleep(no*2);
        block();
    });
}

Salida (con marcas de tiempo de una ejecución):

12:57:02.574  2 starting
12:57:02.574  1 starting
12:57:04.590  1 done
12:57:06.590  2 done
12:57:06.591  finally!

1
@ ɲeuroburɳ El código anterior espera en el hilo principal. Creo que esto bloqueará el hilo principal y hará que la interfaz de usuario no responda hasta que todo el grupo esté completo. Recomiendo mover la espera a un hilo de fondo. Por ejemplo, dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_HIGH, 0)
cbartel

2
@cbartel, buena captura! He actualizado el código de ejemplo para reflejar tu comentario. Muchas veces necesita que la devolución de llamada esté en la cola principal, en ese caso, aunque dispatch_queue_notifyprobablemente sea mejor (a menos que se garantice que el tiempo de bloqueo sea corto).
ɲeuroburɳ

¿Dónde puedo liberar el grupo (es decir, dispatch_release (grupo))? No estoy seguro de si es seguro liberarlo en dispatch_group_notify. Pero dado que ese es el código que se ejecuta después de que el grupo está completo, no estoy seguro de dónde lanzar.
GingerBreadMane

Si está utilizando ARC, entonces no necesita llamar a dispatch_release: stackoverflow.com/questions/8618632/…
ɲeuroburɳ

3
Nice post que explica además que: commandshift.co.uk/blog/2014/03/19/...
Rizon

96

Con Swift 5.1, Grand Central Dispatch ofrece muchas formas de resolver su problema. Según sus necesidades, puede elegir uno de los siete patrones que se muestran en los siguientes fragmentos de Playground.


# 1 El uso de DispatchGroup, DispatchGroup's notify(qos:flags:queue:execute:), y DispatchQueue' sasync(group:qos:flags:execute:)

La Guía de programación de concurrencia para desarrolladores de Apple establece lo siguienteDispatchGroup :

Los grupos de despacho son una forma de bloquear un hilo hasta que una o más tareas terminen de ejecutarse. Puede utilizar este comportamiento en lugares donde no puede avanzar hasta que se completen todas las tareas especificadas. Por ejemplo, después de enviar varias tareas para calcular algunos datos, puede usar un grupo para esperar esas tareas y luego procesar los resultados cuando estén listas.

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)
let group = DispatchGroup()

queue.async(group: group) {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
}

queue.async(group: group) {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
}

group.notify(queue: queue) {
    print("#3 finished")
}

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 */

# 2 El uso de DispatchGroup, DispatchGroup's wait(), DispatchGroup' s enter(), y DispatchGroup'sleave()

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)
let group = DispatchGroup()

group.enter()
queue.async {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
    group.leave()
}

group.enter()
queue.async {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
    group.leave()
}

queue.async {
    group.wait()
    print("#3 finished")
}

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 */

Tenga en cuenta que también puede mezclar DispatchGroup wait()con DispatchQueue async(group:qos:flags:execute:)o mezclar DispatchGroup enter()y DispatchGroup leave()con DispatchGroup notify(qos:flags:queue:execute:).


# 3 El uso y la 'sDispatch​Work​Item​Flags barrierDispatchQueueasync(group:qos:flags:execute:)

El tutorial Grand Central Dispatch para Swift 4: Parte 1/2 del artículo de Raywenderlich.com da una definición de barreras :

Las barreras de envío son un grupo de funciones que actúan como un cuello de botella de estilo en serie cuando se trabaja con colas concurrentes. Cuando envía un mensaje DispatchWorkItema una cola de despacho, puede establecer marcas para indicar que debe ser el único elemento ejecutado en la cola especificada para ese momento en particular. Esto significa que todos los artículos enviados a la cola antes de la barrera de envío deben completarse antes de que DispatchWorkItemse ejecute.

Uso:

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)

queue.async {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
}

queue.async {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
}

queue.async(flags: .barrier) {
    print("#3 finished")
}

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 */

# 4. El uso de DispatchWorkItem, Dispatch​Work​Item​Flags's barrier, y DispatchQueue' sasync(execute:)

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)

queue.async {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
}

queue.async {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
}

let dispatchWorkItem = DispatchWorkItem(qos: .default, flags: .barrier) {
    print("#3 finished")
}

queue.async(execute: dispatchWorkItem)

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 */

# 5. El uso de DispatchSemaphore, DispatchSemaphore's wait(), y DispatchSemaphore' ssignal()

Soroush Khanlou escribió las siguientes líneas en la publicación del blog The GCD Handbook :

Usando un semáforo, podemos bloquear un hilo por una cantidad arbitraria de tiempo, hasta que se envíe una señal de otro hilo. Los semáforos, como el resto de GCD, son seguros para subprocesos y se pueden activar desde cualquier lugar. Los semáforos se pueden usar cuando hay una API asincrónica que necesita hacer sincrónica, pero no puede modificarla.

Apple Developer API Reference también ofrece la siguiente discusión para el DispatchSemaphore init(value:​)inicializador:

Pasar cero para el valor es útil para cuando dos hilos necesitan conciliar la finalización de un evento en particular. Pasar un valor mayor que cero es útil para administrar un grupo finito de recursos, donde el tamaño del grupo es igual al valor.

Uso:

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)
let semaphore = DispatchSemaphore(value: 0)

queue.async {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
    semaphore.signal()
}

queue.async {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
    semaphore.signal()
}

queue.async {
    semaphore.wait()
    semaphore.wait()    
    print("#3 finished")
}

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 */

# 6. El uso OperationQueuey la Operation'saddDependency(_:)

La Referencia de la API para desarrolladores de Apple establece sobre Operation​Queue:

Las colas de operaciones usan la libdispatchbiblioteca (también conocida como Grand Central Dispatch) para iniciar la ejecución de sus operaciones.

Uso:

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let operationQueue = OperationQueue()

let blockOne = BlockOperation {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
}

let blockTwo = BlockOperation {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
}

let blockThree = BlockOperation {
    print("#3 finished")
}

blockThree.addDependency(blockOne)
blockThree.addDependency(blockTwo)

operationQueue.addOperations([blockThree, blockTwo, blockOne], waitUntilFinished: false)

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 or
 #2 started
 #1 started
 #2 finished
 #1 finished
 #3 finished
 */

# 7. El uso de OperationQueuey OperationQueue's addBarrierBlock(_:)(requiere iOS 13)

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let operationQueue = OperationQueue()

let blockOne = BlockOperation {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
}

let blockTwo = BlockOperation {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
}

operationQueue.addOperations([blockTwo, blockOne], waitUntilFinished: false)
operationQueue.addBarrierBlock {
    print("#3 finished")
}

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 or
 #2 started
 #1 started
 #2 finished
 #1 finished
 #3 finished
 */

¿Existe una solución para las llamadas asíncronas sin usar group.enter () y group.leave () para cada una (y sin semáforos)? Me gusta Si necesito esperar una solicitud asíncrona a un servidor, luego espere una segunda solicitud asíncrona y así sucesivamente. He leído este artículo avanderlee.com/swift/asynchronous-operations pero no veo un uso simple en comparación con BlockOperation
Woof

58

Otra alternativa de GCD es una barrera:

dispatch_queue_t queue = dispatch_queue_create("com.company.app.queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{ 
    NSLog(@"start one!\n");  
    sleep(4);  
    NSLog(@"end one!\n");
});

dispatch_async(queue, ^{  
    NSLog(@"start two!\n");  
    sleep(2);  
    NSLog(@"end two!\n"); 
});

dispatch_barrier_async(queue, ^{  
    NSLog(@"Hi, I'm the final block!\n");  
});

Simplemente cree una cola simultánea, envíe sus dos bloques y luego envíe el bloque final con barrera, lo que hará que espere a que los otros dos terminen.


¿Hay algún problema si no usé sleep (4);
Himanth

No, por supuesto, no hay problema con eso. De hecho, ¡prácticamente nunca quieres hacerlo sleep()! Solo agregué esas sleep()llamadas por razones pedagógicas, para que los bloques se ejecuten el tiempo suficiente para que pueda ver que se ejecutan simultáneamente. En este ejemplo trivial, en ausencia de sleep(), estos dos bloques pueden ejecutarse tan rápido que el bloque despachado puede comenzar y finalizar antes de que tenga la oportunidad de observar empíricamente la ejecución concurrente. Pero no lo hagas sleep()en tu propio código.
Rob

39

Sé que preguntaste por GCD, pero si querías, NSOperationQueuetambién maneja este tipo de cosas con mucha gracia, por ejemplo:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Starting 3");
}];

NSOperation *operation;

operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Starting 1");
    sleep(7);
    NSLog(@"Finishing 1");
}];

[completionOperation addDependency:operation];
[queue addOperation:operation];

operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Starting 2");
    sleep(5);
    NSLog(@"Finishing 2");
}];

[completionOperation addDependency:operation];
[queue addOperation:operation];

[queue addOperation:completionOperation];

3
Esto está bien cuando el código dentro de su NSBlockOperation es sincrónico. Pero, ¿qué pasa si no es así y desea activar la finalización cuando finalice su operación asincrónica?
Greg Maletic

3
@GregMaletic En ese caso, hago una NSOperationsubclase que es concurrente y se establece isFinishedcuando se completa el proceso asincrónico. Entonces las dependencias funcionan bien.
Rob


1
@GregMaletic Sí, también puedes usar eso (mientras dispatch_semaphore_waitno tenga lugar en la cola principal y mientras tus señales y esperas estén equilibradas). Siempre que no bloquee la cola principal, un enfoque de semáforo está bien, si no necesita la flexibilidad de las operaciones (por ejemplo, tener la capacidad de cancelarlas, la capacidad de controlar el grado de concurrencia, etc.).
Rob

1
@ Reza.Ab: si necesita que la tarea uno termine antes de que comience la tarea dos, agregue una dependencia entre esas tareas. O si la cola está siempre solamente interpretando una tarea a la vez, lo convierten en una cola serie activando maxConcurrentOperationCounta 1. También puede establecer la prioridad de las operaciones, tanto la qualityOfServicey queuePriority, pero estas tienen un impacto mucho más sutil en la prioridad de la tarea que las dependencias y / o el grado de concurrencia de la cola.
Rob

4

Las respuestas anteriores son geniales, pero todas se perdieron una cosa. group ejecuta tareas (bloques) en el hilo donde ingresó cuando usa dispatch_group_enter/ dispatch_group_leave.

- (IBAction)buttonAction:(id)sender {
      dispatch_queue_t demoQueue = dispatch_queue_create("com.demo.group", DISPATCH_QUEUE_CONCURRENT);
      dispatch_async(demoQueue, ^{
        dispatch_group_t demoGroup = dispatch_group_create();
        for(int i = 0; i < 10; i++) {
          dispatch_group_enter(demoGroup);
          [self testMethod:i
                     block:^{
                       dispatch_group_leave(demoGroup);
                     }];
        }

        dispatch_group_notify(demoGroup, dispatch_get_main_queue(), ^{
          NSLog(@"All group tasks are done!");
        });
      });
    }

    - (void)testMethod:(NSInteger)index block:(void(^)(void))completeBlock {
      NSLog(@"Group task started...%ld", index);
      NSLog(@"Current thread is %@ thread", [NSThread isMainThread] ? @"main" : @"not main");
      [NSThread sleepForTimeInterval:1.f];

      if(completeBlock) {
        completeBlock();
      }
    }

esto se ejecuta en la cola concurrente creada demoQueue. Si no creo ninguna cola, se ejecuta en el hilo principal .

- (IBAction)buttonAction:(id)sender {
    dispatch_group_t demoGroup = dispatch_group_create();
    for(int i = 0; i < 10; i++) {
      dispatch_group_enter(demoGroup);
      [self testMethod:i
                 block:^{
                   dispatch_group_leave(demoGroup);
                 }];
    }

    dispatch_group_notify(demoGroup, dispatch_get_main_queue(), ^{
      NSLog(@"All group tasks are done!");
    });
    }

    - (void)testMethod:(NSInteger)index block:(void(^)(void))completeBlock {
      NSLog(@"Group task started...%ld", index);
      NSLog(@"Current thread is %@ thread", [NSThread isMainThread] ? @"main" : @"not main");
      [NSThread sleepForTimeInterval:1.f];

      if(completeBlock) {
        completeBlock();
      }
    }

y hay una tercera forma de hacer que las tareas se ejecuten en otro hilo:

- (IBAction)buttonAction:(id)sender {
      dispatch_queue_t demoQueue = dispatch_queue_create("com.demo.group", DISPATCH_QUEUE_CONCURRENT);
      //  dispatch_async(demoQueue, ^{
      __weak ViewController* weakSelf = self;
      dispatch_group_t demoGroup = dispatch_group_create();
      for(int i = 0; i < 10; i++) {
        dispatch_group_enter(demoGroup);
        dispatch_async(demoQueue, ^{
          [weakSelf testMethod:i
                         block:^{
                           dispatch_group_leave(demoGroup);
                         }];
        });
      }

      dispatch_group_notify(demoGroup, dispatch_get_main_queue(), ^{
        NSLog(@"All group tasks are done!");
      });
      //  });
    }

Por supuesto, como se mencionó, puede usar dispatch_group_asyncpara obtener lo que desea.


3

La primera respuesta es esencialmente correcta, pero si desea la forma más simple de lograr el resultado deseado, aquí hay un ejemplo de código independiente que muestra cómo hacerlo con un semáforo (que también es cómo funcionan los grupos de despacho detrás de escena, JFYI) :

#include <dispatch/dispatch.h>
#include <stdio.h>

main()
{
        dispatch_queue_t myQ = dispatch_queue_create("my.conQ", DISPATCH_QUEUE_CONCURRENT);
        dispatch_semaphore_t mySem = dispatch_semaphore_create(0);

        dispatch_async(myQ, ^{ printf("Hi I'm block one!\n"); sleep(2); dispatch_semaphore_signal(mySem);});
        dispatch_async(myQ, ^{ printf("Hi I'm block two!\n"); sleep(4); dispatch_semaphore_signal(mySem);});
        dispatch_async(myQ, ^{ dispatch_semaphore_wait(mySem, DISPATCH_TIME_FOREVER); printf("Hi, I'm the final block!\n"); });
        dispatch_main();
}

77
Dos observaciones: 1. Te estás perdiendo a dispatch_semaphore_wait. Tienes dos señales, por lo que necesitas dos esperas. Tal como está, su bloque de "finalización" comenzará tan pronto como el primer bloque indique el semáforo, pero antes de que finalice el otro bloque; 2. Dado que esta era una pregunta de iOS, desalentaría el uso de dispatch_main.
Rob

1
Estoy de acuerdo con Rob Esta no es una solución válida. Se dispatch_semaphore_waitdesbloqueará tan pronto como dispatch_semaphore_signalse llame a cualquiera de los métodos. La razón por la que puede parecer que esto funciona es que los printfbloques 'uno' y 'dos' ocurren inmediatamente, y printfel 'finalmente' ocurre después de una espera, por lo tanto, después de que el bloque uno ha dormido durante 2 segundos. Si coloca el printf después de las sleepllamadas, obtendrá el resultado para 'uno', luego 2 segundos después para 'finalmente', luego 2 segundos más tarde para 'dos'.
uroeuroburɳ

1

Respuesta aceptada rápidamente:

let group = DispatchGroup()

group.async(group: DispatchQueue.global(qos: .default), execute: {
    // block1
    print("Block1")
    Thread.sleep(forTimeInterval: 5.0)
    print("Block1 End")
})


group.async(group: DispatchQueue.global(qos: .default), execute: {
    // block2
    print("Block2")
    Thread.sleep(forTimeInterval: 8.0)
    print("Block2 End")
})

dispatch_group_notify(group, DispatchQueue.global(qos: .default), {
    // block3
    print("Block3")
})

// only for non-ARC projects, handled automatically in ARC-enabled projects.
dispatch_release(group)

0

Ejemplo de Swift 4.2:

let group = DispatchGroup.group(count: 2)
group.notify(queue: DispatchQueue.main) {
     self.renderingLine = false
     // all groups are done
}
DispatchQueue.main.async {
    self.renderTargetNode(floorPosition: targetPosition, animated: closedContour) {
        group.leave()
        // first done
    }
    self.renderCenterLine(position: targetPosition, animated: closedContour) {
        group.leave()
        // second done
    }
 }

group.leave()causó accidente
Ben

-3

Por no decir que otras respuestas no son excelentes para ciertas circunstancias, pero este es un fragmento que siempre uso en Google:

- (void)runSigninThenInvokeSelector:(SEL)signInDoneSel {


    if (signInDoneSel) {
        [self performSelector:signInDoneSel];
    }

}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.