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

101

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

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

cfischer
quelle
Warum dispatch_get_current_queue()ist das in iOS 6 veraltet? Die Dokumente sagen nichts darüber
nur
3
Der Compiler beschwert sich darüber. Versuch es.
cfischer
4
@jere Überprüfen Sie die Header-Datei, es gibt an, dass es beraubt ist
WDUK
Abgesehen von Diskussionen über bewährte Verfahren sehe ich [NSOperationQueue currentQueue], das die Frage beantworten kann. Ich bin mir nicht sicher, welche Einschränkungen bei der Verwendung bestehen.
Matt
Vorbehalt gefunden ------ [NSOperationQueue currentQueue] nicht identisch mit dispatch_get_current_queue () ----- Manchmal wird null zurückgegeben ---- dispatch_async (dispatch_get_global_queue (0, 0), ^ {NSLog (@ "q (0, 0) ist% @ ", dispatch_get_current_queue ()); NSLog (@" cq (0,0) ist% @ ", [NSOperationQueue currentQueue]);}); ----- q (0,0) ist <OS_dispatch_queue_root: com.apple.root.default-qos [0x100195140]> cq (0,0) ist (null) ----- beraubt oder nicht dispatch_get_current_queue () scheint um die einzige Lösung zu sein, die ich sehe, um die aktuelle Warteschlange unter allen Bedingungen zu melden
godzilla

Antworten:

64

Das Muster "In der Warteschlange ausführen, in der sich der Anrufer befand" 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 hierfür ist zu sagen, dass "der Abschlussblock in einer implementierungsdefinierten Warteschlange mit den folgenden Eigenschaften ausgeführt wird: x, y, z" und den Block an eine bestimmte Warteschlange senden zu lassen, wenn der Aufrufer mehr Kontrolle als diese wünscht. Ein typischer Satz von Eigenschaften, die angegeben werden müssen, ist "seriell, nicht wiedereintrittsfähig und asynchron in Bezug auf jede andere für Anwendungen sichtbare Warteschlange".

** BEARBEITEN **

Catfish_Man hat in den Kommentaren unten ein Beispiel angegeben. 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); 
    } 
}
Catfish_Man
quelle
7
Total einverstanden. Sie können sehen, dass Apple dies immer befolgt. Wenn Sie etwas in der Hauptwarteschlange tun möchten, müssen Sie immer in die Hauptwarteschlange senden, da Apple immer garantiert, dass Sie sich in einem anderen Thread befinden. Die meiste Zeit warten Sie auf einen lang laufenden Prozess, um das Abrufen / Bearbeiten von Daten abzuschließen. Anschließend können Sie diese im Hintergrund direkt in Ihrem Abschlussblock verarbeiten und dann nur UI-Aufrufe in einen Versandblock in der Hauptwarteschlange stecken. Außerdem ist es immer gut, den Erwartungen von Apple zu folgen, da Entwickler an das Muster gewöhnt sind.
Jack Lawrence
1
Tolle Antwort ... aber ich hatte gehofft, zumindest einen Beispielcode zu haben, um zu veranschaulichen, was Sie sagen
Abbood
3
- (void) aMethodWithCompletionBlock: (dispatch_block_t) finishHandler {dispatch_async (self.workQueue, ^ {[self doSomeWork]; dispatch_async (self.callbackQueue, finishHandler);}}
Catfish_Man
(Für ein völlig triviales Beispiel)
Catfish_Man
3
Im allgemeinen Fall ist dies nicht möglich, da es aufgrund von dispatch_sync () und dispatch_set_target_queue () möglich (und tatsächlich ziemlich wahrscheinlich) ist, sich gleichzeitig in mehr als einer Warteschlange zu befinden. Es gibt einige Teilmengen des allgemeinen Falls, die möglich sind.
Catfish_Man
27

Dies ist grundsätzlich der falsche Ansatz für die von Ihnen beschriebene API. Wenn eine API einen Block und einen Abschlussblock zum Ausführen akzeptiert, müssen die folgenden Fakten zutreffen:

  1. Der "auszuführende Block" sollte in einer internen Warteschlange ausgeführt werden, z. B. einer Warteschlange, die für die API privat ist und daher vollständig unter der Kontrolle dieser API steht. 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, es sei denn, die gleichen Annahmen wie für Nr. 1 gelten, z. B. wird der Abschlussblock in einer bekannten globalen Warteschlange ausgeführt. Der Abschlussblock sollte außerdem asynchron in der übergebenen Warteschlange gesendet werden.

Dies sind nicht nur Stilpunkte, sondern unbedingt erforderlich, wenn Ihre API vor Deadlocks oder anderen Randfällen geschützt werden soll, die Sie sonst eines Tages am nächsten Baum hängen lassen. :-)

jkh
quelle
11
Klingt vernünftig, aber aus irgendeinem Grund ist dies nicht der Ansatz von Apple für ihre eigenen APIs: Die meisten Methoden, die einen Abschlussblock benötigen, nehmen keine Warteschlange ein ...
cfischer
2
Richtig, und um meine vorherige Behauptung etwas zu ändern, wenn es offensichtlich ist, dass der Abschlussblock in der Hauptwarteschlange oder einer globalen gleichzeitigen Warteschlange ausgeführt wird. Ich werde meine Antwort ändern, um dies anzuzeigen.
JKH
Um zu kommentieren, dass Apple diesen Ansatz nicht verfolgt: Apple ist per Definition nicht immer "richtig". Richtige Argumente sind immer stärker als eine bestimmte Autorität, die jeder Wissenschaftler bestätigen wird. Ich denke, die obige Antwort sagt es aus Sicht der richtigen Softwarearchitektur sehr gut aus.
Werner Altewischer
14

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

- (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 sie auf dem anderen Thread zu versenden.

Dan Rosenstark
quelle
12

Sie sollten dispatch_get_current_queuein erster Linie vorsichtig mit Ihrer Verwendung sein . Aus der Header-Datei:

Nur zum Debuggen und Protokollieren empfohlen:

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 eines von zwei Dingen tun:

  1. Behalten Sie einen Verweis auf die Warteschlange bei, in der Sie ursprünglich gepostet haben (sofern Sie ihn über erstellt haben dispatch_queue_create), und verwenden Sie diesen fortan.

  2. Verwenden Sie systemdefinierte Warteschlangen über dispatch_get_global_queueund 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 effektiv selbst tun.

WDUK
quelle
16
Wie können wir "einen Verweis auf die Warteschlange behalten, in der Sie ursprünglich gepostet haben", wenn wir nicht dispatch_get_current_queue()herausfinden können, um welche Warteschlange es sich handelt? Manchmal hat der Code, der wissen muss, in welcher Warteschlange er ausgeführt wird, keine Kontrolle oder Kenntnis davon. Ich habe viel Code, der in einer Hintergrundwarteschlange ausgeführt werden kann (und sollte), muss aber gelegentlich die GUI (Fortschrittsbalken usw.) aktualisieren und muss daher dispatch_sync () für diese Vorgänge in die Hauptwarteschlange übertragen. Wenn sich dispatch_sync () bereits in der Hauptwarteschlange befindet, wird es für immer gesperrt. Ich werde Monate brauchen, um meinen Code dafür umzugestalten.
Abhi Beckert
3
Ich denke, NSURLConnection gibt seine Abschlussrückrufe auf demselben Thread zurück, von dem aus es aufgerufen wird. Würde es dieselbe API "dispatch_get_current_queue" verwenden, um die Warteschlange zu speichern, aus der es aufgerufen wird, um zum Zeitpunkt des Rückrufs verwendet zu werden?
Defactodeity
5

Apple war veraltet dispatch_get_current_queue(), hat aber an anderer Stelle ein Loch hinterlassen, sodass wir immer noch 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 diese underlyingQueueEigenschaft seit iOS 8 verfügbar ist.

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

kelin
quelle
4

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

alexey.hippie
quelle
0

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

Wir haben eine Serviceschicht und die UI-Schicht (unter anderem). Die Serviceschicht führt Aufgaben im Hintergrund aus. (Datenmanipulationsaufgaben, CoreData-Aufgaben, Netzwerkaufrufe usw.). Die Serviceschicht verfügt über einige 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 dann einen Erfolgsabschlussblock 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 garantieren wir, dass die Blöcke, die an die Serviceschicht übergeben werden, in der Warteschlange versendet 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 dieser Eigenschaft.

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

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

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

Kris Subramanian
quelle