wake-up-neo.com

Alternativen zu dispatch_get_current_queue () für Abschlussblöcke in iOS 6?

Ich habe eine Methode, die einen Block und einen Abschlussblock akzeptiert. Der erste Block sollte im Hintergrund ausgeführt werden, während der Beendigungsblock in der Warteschlange ausgeführt werden sollte, in der die Methode aufgerufen wurde.

Für letztere habe ich immer dispatch_get_current_queue() verwendet, aber es scheint, dass es in iOS 6 oder höher veraltet ist. Was soll ich stattdessen verwenden?

98
cfischer

Das Muster "Auf welcher Warteschlange auch immer der Anrufer war" ist ansprechend, aber letztendlich keine gute Idee. Diese Warteschlange kann eine Warteschlange mit niedriger Priorität, die Hauptwarteschlange oder eine andere Warteschlange mit ungeraden Eigenschaften sein.

Mein bevorzugter Ansatz ist, zu sagen, dass der Beendigungsblock in einer implementierungsdefinierten Warteschlange mit den folgenden Eigenschaften ausgeführt wird: x, y, z, und dass der Block an eine bestimmte Warteschlange gesendet wird, wenn der Aufrufer mehr Kontrolle wünscht. Ein typischer Satz von Eigenschaften, die angegeben werden müssen, ist etwa "seriell, nicht wiedereintrittsfähig und asynchron in Bezug auf jede andere für die Anwendung sichtbare Warteschlange".

** EDIT **

Catfish_Man hat in den Kommentaren unten ein Beispiel eingefügt. Ich füge es nur seiner Antwort hinzu.

- (void) aMethodWithCompletionBlock:(dispatch_block_t)completionHandler     
{ 
    dispatch_async(self.workQueue, ^{ 
        [self doSomeWork]; 
        dispatch_async(self.callbackQueue, completionHandler); 
    } 
}
63
Catfish_Man

Dies ist grundsätzlich der falsche Ansatz für die von Ihnen beschriebene API. Wenn eine API einen Block und einen Abschlussblock akzeptiert, um ausgeführt zu werden, müssen die folgenden Fakten zutreffen:

  1. Der "auszuführende Block" sollte in einer internen Warteschlange ausgeführt werden, z. Eine Warteschlange, die für die API privat ist und daher vollständig der API-Steuerung unterliegt. Die einzige Ausnahme besteht darin, dass die API ausdrücklich deklariert, dass der Block in der Hauptwarteschlange oder einer der globalen gleichzeitigen Warteschlangen ausgeführt wird.

  2. Der Abschlussblock sollte immer als Tupel (Warteschlange, Block) ausgedrückt werden, sofern nicht die gleichen Annahmen wie für # 1 zutreffen, z. Der Abschlussblock wird in einer bekannten globalen Warteschlange ausgeführt. Der Abschlussblock sollte außerdem asynchron in der übergebenen Warteschlange verteilt werden.

Dies sind nicht nur Stilmerkmale, sondern sie sind absolut notwendig, wenn Ihre API vor Deadlocks oder anderem Edge-Case-Verhalten geschützt werden soll, das Sie ansonsten eines Tages vom nächsten Baum hängen wird. :-)

24
jkh

Die anderen Antworten sind großartig, aber für mich ist die Antwort strukturell. Ich habe eine Methode wie diese, die auf einem Singleton ist:

- (void) dispatchOnHighPriorityNonMainQueue:(simplest_block)block forceAsync:(BOOL)forceAsync {
    if (forceAsync || [NSThread isMainThread])
        dispatch_async_on_high_priority_queue(block);
    else
        block();
}

das hat zwei Abhängigkeiten, die sind:

static void dispatch_async_on_high_priority_queue(dispatch_block_t block) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), block);
}

und

typedef void (^simplest_block)(void); // also could use dispatch_block_t

Auf diese Weise zentralisiere ich meine Anrufe, um auf dem anderen Thread zu versenden.

14
Dan Rosenstark

Sie sollten bei der Verwendung von dispatch_get_current_queue An erster Stelle vorsichtig sein. Aus der Header-Datei:

Empfohlen nur für Debug- und Protokollierungszwecke:

Der Code darf keine Annahmen über die zurückgegebene Warteschlange treffen, es sei denn, es handelt sich um eine der globalen Warteschlangen oder eine Warteschlange, die der Code selbst erstellt hat. Der Code darf nicht davon ausgehen, dass die synchrone Ausführung in einer Warteschlange vor einem Deadlock geschützt ist, wenn diese Warteschlange nicht von dispatch_get_current_queue () zurückgegeben wird.

Sie können eine der beiden folgenden Aktionen ausführen:

  1. Behalten Sie einen Verweis auf die Warteschlange bei, in der Sie ursprünglich gepostet haben (falls Sie sie über dispatch_queue_create Erstellt haben), und verwenden Sie diese von da an.

  2. Verwenden Sie systemdefinierte Warteschlangen mit dispatch_get_global_queue Und verfolgen Sie, welche Sie verwenden.

Während Sie sich zuvor auf das System verlassen haben, um die Warteschlange zu verfolgen, in der Sie sich befinden, müssen Sie dies zunächst selbst tun.

12
WDUK

Für diejenigen, die noch einen Warteschlangenvergleich benötigen, können Sie Warteschlangen nach ihrer Bezeichnung oder Angabe vergleichen. Überprüfen Sie dies https://stackoverflow.com/a/23220741/1531141

4
alexey.hippie

Apple hatte dispatch_get_current_queue() abgelehnt, aber an anderer Stelle eine Lücke gelassen, sodass wir weiterhin die aktuelle Versandwarteschlange abrufen können:

if let currentDispatch = OperationQueue.current?.underlyingQueue {
    print(currentDispatch)
    // Do stuff
}

Dies funktioniert zumindest für die Hauptwarteschlange. Beachten Sie, dass die Eigenschaft underlyingQueue seit iOS 8 verfügbar ist.

Wenn Sie den Abschlussblock in der ursprünglichen Warteschlange ausführen müssen, können Sie auch OperationQueue direkt verwenden, ich meine ohne GCD.

4
kelin

Dies ist eine Antwort von mir auch. Also werde ich über unseren Anwendungsfall sprechen.

Wir haben eine Service-Schicht und die UI-Schicht (unter anderem). Die Dienstschicht führt Aufgaben im Hintergrund aus. (Datenmanipulationsaufgaben, CoreData-Aufgaben, Netzwerkaufrufe usw.). Die Serviceschicht verfügt über mehrere Operationswarteschlangen, um die Anforderungen der UI-Schicht zu erfüllen.

Die UI-Schicht verlässt sich auf die Services-Schicht, um ihre Arbeit zu erledigen und anschließend einen Block zum erfolgreichen Abschluss auszuführen. Dieser Block kann UIKit-Code enthalten. Ein einfacher Anwendungsfall besteht darin, alle Nachrichten vom Server abzurufen und die Sammlungsansicht neu zu laden.

Hier stellen wir sicher, dass die Blöcke, die an die Serviceschicht übergeben werden, in der Warteschlange abgesetzt werden, in der der Service aufgerufen wurde. Da dispatch_get_current_queue eine veraltete Methode ist, verwenden wir NSOperationQueue.currentQueue, um die aktuelle Warteschlange des Anrufers abzurufen. Wichtiger Hinweis zu diesem Objekt.

Das Aufrufen dieser Methode von außerhalb des Kontexts einer ausgeführten Operation führt normalerweise dazu, dass nil zurückgegeben wird.

Da wir unsere Dienste immer in einer bekannten Warteschlange aufrufen (Unsere benutzerdefinierten Warteschlangen und die Hauptwarteschlange), funktioniert dies für uns gut. Es gibt Fälle, in denen serviceA serviceB aufrufen kann, der serviceC aufrufen kann. Da wir steuern, woher der erste Serviceabruf kommt, wissen wir, dass der Rest der Services denselben Regeln folgt.

Daher gibt NSOperationQueue.currentQueue immer eine unserer Warteschlangen oder die Hauptwarteschlange zurück.

0