Ich teste Code, der asynchrone Verarbeitung mit Grand Central Dispatch ausführt. Der Testcode sieht folgendermaßen aus:
[object runSomeLongOperationAndDo:^{
STAssert…
}];
Die Tests müssen warten, bis der Vorgang abgeschlossen ist. Meine aktuelle Lösung sieht folgendermaßen aus:
__block BOOL finished = NO;
[object runSomeLongOperationAndDo:^{
STAssert…
finished = YES;
}];
while (!finished);
Was ein bisschen grob aussieht, kennst du einen besseren Weg? Ich könnte die Warteschlange freigeben und dann blockieren, indem ich aufrufe dispatch_sync
:
[object runSomeLongOperationAndDo:^{
STAssert…
}];
dispatch_sync(object.queue, ^{});
… Aber das macht vielleicht zu viel auf der object
.
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
mitwhile (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]; }
Zusätzlich zu der Semaphorentechnik, die in anderen Antworten ausführlich behandelt wird, können wir jetzt XCTest in Xcode 6 verwenden, um asynchrone Tests über durchzuführen
XCTestExpectation
. Dadurch werden beim Testen von asynchronem Code keine Semaphoren benötigt. Beispielsweise:Für zukünftige Leser ist die Versand-Semaphor-Technik zwar eine wunderbare Technik, wenn sie unbedingt benötigt wird, aber ich muss gestehen, dass zu viele neue Entwickler, die mit guten asynchronen Programmiermustern nicht vertraut sind, zu schnell zu Semaphoren als allgemeinem Mechanismus für die asynchrone Erstellung tendieren Routinen verhalten sich synchron. Schlimmer noch, ich habe gesehen, dass viele von ihnen diese Semaphor-Technik aus der Hauptwarteschlange verwenden (und wir sollten die Hauptwarteschlange in Produktions-Apps niemals blockieren).
Ich weiß, dass dies hier nicht der Fall ist (als diese Frage gestellt wurde, gab es kein nettes Tool wie
XCTestExpectation
; außerdem müssen wir in diesen Testsuiten sicherstellen, dass der Test erst beendet wird, wenn der asynchrone Aufruf abgeschlossen ist). Dies ist eine der seltenen Situationen, in denen die Semaphortechnik zum Blockieren des Hauptthreads erforderlich sein kann.Mit meiner Entschuldigung an den Autor dieser ursprünglichen Frage, für den die Semaphortechnik fundiert ist, schreibe ich diese Warnung an alle neuen Entwickler, die diese Semaphortechnik sehen und in Betracht ziehen, sie in ihrem Code als allgemeinen Ansatz für den Umgang mit asynchronem Verhalten anzuwenden Methoden: Seien Sie gewarnt, dass neun von zehn die Semaphor-Technik nicht istDer beste Ansatz bei der Erfassung asynchroner Vorgänge. Machen Sie sich stattdessen mit Abschlussblock- / Abschlussmustern sowie mit Delegiertenprotokollmustern und Benachrichtigungen vertraut. Dies sind oft viel bessere Möglichkeiten, um mit asynchronen Aufgaben umzugehen, als Semaphoren zu verwenden, damit sie sich synchron verhalten. Normalerweise gibt es gute Gründe dafür, dass asynchrone Aufgaben so konzipiert sind, dass sie sich asynchron verhalten. Verwenden Sie daher das richtige asynchrone Muster, anstatt zu versuchen, sie synchron zu verhalten.
quelle
NSOperationQueue
. Wenn ich nicht so etwas wie ein Semaphor verwende,NSOperation
werden alle Dokumentendownloads sofort abgeschlossen und es wird keine echte Warteschlange für Downloads geben - sie werden so ziemlich gleichzeitig ablaufen, was ich nicht möchte. Sind Semaphoren hier sinnvoll? Oder gibt es eine bessere Möglichkeit, NSOperations auf das asynchrone Ende anderer warten zu lassen? Oder etwas anderes?AFHTTPRequestOperation
Objekte hinzufügen , sollten Sie einfach eine Abschlussoperation erstellen (die Sie von den anderen Operationen abhängig machen). Oder verwenden Sie Versandgruppen. Übrigens, Sie sagen, Sie möchten nicht, dass sie gleichzeitig ausgeführt werden, was in Ordnung ist, wenn Sie dies benötigen, aber Sie zahlen ernsthafte Leistungseinbußen, wenn Sie dies nacheinander und nicht gleichzeitig tun. Ich benutze inmaxConcurrentOperationCount
der Regel 4 oder 5.Ich bin kürzlich wieder zu diesem Thema gekommen und habe die folgende Kategorie geschrieben über
NSObject
:Auf diese Weise kann ich einen asynchronen Anruf mit einem Rückruf in Tests leicht in einen synchronen verwandeln:
quelle
Verwenden Sie im Allgemeinen keine dieser Antworten, sie werden oft nicht skaliert (es gibt hier und da Ausnahmen, klar)
Diese Ansätze sind nicht kompatibel mit der Funktionsweise von GCD und verursachen entweder Deadlocks und / oder töten die Batterie durch ununterbrochenes Abrufen.
Mit anderen Worten, ordnen Sie Ihren Code so um, dass nicht synchron auf ein Ergebnis gewartet wird, sondern dass ein Ergebnis über eine Statusänderung benachrichtigt wird (z. B. Rückrufe / Delegatenprotokolle, Verfügbarkeit, Weggehen, Fehler usw.). (Diese können in Blöcke umgewandelt werden, wenn Sie die Rückrufhölle nicht mögen.) Auf diese Weise können Sie dem Rest der App echtes Verhalten zeigen, anstatt es hinter einer falschen Fassade zu verbergen.
Verwenden Sie stattdessen NSNotificationCenter und definieren Sie ein benutzerdefiniertes Delegatenprotokoll mit Rückrufen für Ihre Klasse. Und wenn Sie nicht gerne überall mit delegierten Rückrufen herumspielen, schließen Sie sie in eine konkrete Proxy-Klasse ein, die das benutzerdefinierte Protokoll implementiert und die verschiedenen Blöcke in den Eigenschaften speichert. Wahrscheinlich bieten auch Convenience-Konstruktoren.
Die anfängliche Arbeit ist etwas mehr, aber sie wird auf lange Sicht die Anzahl der schrecklichen Rennbedingungen und der Umfragen zum Batteriemord verringern.
(Fragen Sie nicht nach einem Beispiel, da es trivial ist und wir die Zeit investieren mussten, um auch die objektiven Grundlagen zu erlernen.)
quelle
Hier ist ein raffinierter Trick, bei dem kein Semaphor verwendet wird:
Warten Sie
dispatch_sync
mit einem leeren Block, um synchron auf eine serielle Versandwarteschlange zu warten, bis der A-Synchron-Block abgeschlossen ist.quelle
Anwendungsbeispiel:
quelle
Es gibt auch SenTestingKitAsync , mit dem Sie Code wie folgt schreiben können:
(Weitere Informationen finden Sie im Artikel objc.io. ) Und seit Xcode 6 gibt es eine
AsynchronousTesting
KategorieXCTest
, in der Sie Code wie diesen schreiben können:quelle
Hier ist eine Alternative zu einem meiner Tests:
quelle
NSCondition
Dokumentation zu-waitUntilDate:
"Sie müssen den Empfänger sperren, bevor Sie diese Methode aufrufen." Also-unlock
sollte das danach sein-waitUntilDate:
.Das hat es für mich getan.
quelle
Manchmal sind auch Timeout-Schleifen hilfreich. Können Sie warten, bis Sie ein (möglicherweise BOOL) Signal von der asynchronen Rückrufmethode erhalten, aber was ist, wenn Sie nie eine Antwort erhalten und aus dieser Schleife ausbrechen möchten? Hier unten finden Sie eine Lösung, die meistens oben beantwortet wurde, jedoch mit einem zusätzlichen Timeout.
quelle
Sehr primitive Lösung des Problems:
quelle
Swift 4:
Verwenden Sie
synchronousRemoteObjectProxyWithErrorHandler
stattremoteObjectProxy
beim Erstellen des Remote-Objekts. Kein Semaphor mehr nötig.Das folgende Beispiel gibt die vom Proxy empfangene Version zurück. Ohne das
synchronousRemoteObjectProxyWithErrorHandler
stürzt es ab (versucht auf nicht zugänglichen Speicher zuzugreifen):quelle
Ich muss warten, bis eine UIWebView geladen ist, bevor ich meine Methode ausführe. Ich konnte dies zum Laufen bringen, indem ich UIWebView-Ready-Checks für den Hauptthread mit GCD in Kombination mit den in diesem Thread erwähnten Semaphormethoden durchführte. Der endgültige Code sieht folgendermaßen aus:
quelle