Testuję kod, który wykonuje przetwarzanie asynchroniczne przy użyciu Grand Central Dispatch. Kod testowy wygląda następująco:
[object runSomeLongOperationAndDo:^{
STAssert…
}];
Testy muszą czekać na zakończenie operacji. Moje obecne rozwiązanie wygląda następująco:
__block BOOL finished = NO;
[object runSomeLongOperationAndDo:^{
STAssert…
finished = YES;
}];
while (!finished);
Co wygląda nieco prymitywnie, znasz lepszy sposób? Mogę odsłonić kolejkę, a następnie zablokować, wywołując dispatch_sync
:
[object runSomeLongOperationAndDo:^{
STAssert…
}];
dispatch_sync(object.queue, ^{});
… Ale to może wystawiać zbyt wiele na object
.
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
zwhile (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]; }
Oprócz techniki semaforów omówionej wyczerpująco w innych odpowiedziach, możemy teraz używać XCTest w Xcode 6 do wykonywania testów asynchronicznych za pośrednictwem
XCTestExpectation
. Eliminuje to konieczność stosowania semaforów podczas testowania kodu asynchronicznego. Na przykład:Ze względu na przyszłych czytelników, chociaż technika semafora wysyłki jest cudowną techniką, gdy jest absolutnie potrzebna, muszę przyznać, że widzę zbyt wielu nowych programistów, niezaznajomionych z dobrymi wzorcami programowania asynchronicznego, zbyt szybko przechodzą na semafory jako ogólny mechanizm tworzenia asynchronicznych procedury zachowują się synchronicznie. Co gorsza, wielu z nich korzysta z tej techniki semaforów z głównej kolejki (i nigdy nie powinniśmy blokować głównej kolejki w aplikacjach produkcyjnych).
Wiem, że tak nie jest w tym przypadku (kiedy to pytanie zostało opublikowane, nie było takiego fajnego narzędzia
XCTestExpectation
; również w tych zestawach testowych musimy upewnić się, że test nie zakończy się, dopóki nie zostanie wykonane wywołanie asynchroniczne). Jest to jedna z tych rzadkich sytuacji, w których może być konieczna technika semaforów do blokowania głównego wątku.Dlatego przepraszam autora tego oryginalnego pytania, dla którego technika semafora jest dobra, piszę to ostrzeżenie do wszystkich nowych programistów, którzy widzą tę technikę semaforów i rozważają zastosowanie jej w kodzie jako ogólnego podejścia do radzenia sobie z asynchronicznym metody: Ostrzegamy, że dziewięć razy na dziesięć technika semaforów jest nienajlepsze podejście w przypadku operacji asynchronicznych. Zamiast tego zapoznaj się z wzorcami blokowania / zamykania, a także wzorcami i powiadomieniami protokołu delegowania. Są to często znacznie lepsze sposoby radzenia sobie z zadaniami asynchronicznymi, niż używanie semaforów, aby zachowywały się synchronicznie. Zwykle istnieją dobre powody, dla których zadania asynchroniczne zostały zaprojektowane tak, aby zachowywały się asynchronicznie, więc należy stosować właściwy wzorzec asynchroniczny zamiast próbować je synchronizować.
źródło
NSOperationQueue
. O ile nie użyję czegoś w rodzaju semafora, wszystkie pobieranie dokumentówNSOperation
natychmiast się zakończy i nie będzie żadnego prawdziwego oczekiwania w kolejce do pobrania - będą postępować równolegle, czego nie chcę. Czy semafory są tutaj rozsądne? Czy jest lepszy sposób, aby NSOperations czekały na asynchroniczny koniec innych? Albo coś innego?AFHTTPRequestOperation
obiekty, powinieneś po prostu utworzyć operację ukończenia (zależną od innych operacji). Lub użyj grup wysyłkowych. BTW, mówisz, że nie chcesz, aby działały równolegle, co jest w porządku, jeśli tego potrzebujesz, ale płacisz poważną karę wydajności wykonując to sekwencyjnie, a nie jednocześnie. Zwykle używammaxConcurrentOperationCount
4 lub 5.Niedawno ponownie dołączyłem do tego problemu i napisałem o następującej kategorii
NSObject
:W ten sposób mogę łatwo przekształcić połączenie asynchroniczne z oddzwanianiem w połączenie synchroniczne w testach:
źródło
Zasadniczo nie używaj żadnej z tych odpowiedzi, często się nie skalują (tu i tam są wyjątki, jasne)
Podejścia te są niezgodne ze sposobem, w jaki GCD ma działać, i doprowadzą do zablokowania i / lub zabicia baterii przez ciągłe odpytywanie.
Innymi słowy, należy zmienić kolejność kodu, tak aby nie było synchronicznego oczekiwania na wynik, ale zamiast tego radzić sobie z powiadomieniem wyniku o zmianie stanu (np. Wywołania zwrotne / protokoły delegowania, dostępność, odejście, błędy itp.). (Można je przekształcić w bloki, jeśli nie lubisz piekła zwrotnego.) Ponieważ w ten sposób możesz ujawnić realne zachowanie reszcie aplikacji, niż ukryć je za fałszywą fasadą.
Zamiast tego użyj NSNotificationCenter , zdefiniuj niestandardowy protokół delegowania z wywołaniami zwrotnymi dla swojej klasy. A jeśli nie lubisz tłumić wywołań zwrotnych delegatów, zawiń je w konkretną klasę proxy, która implementuje niestandardowy protokół i zapisuje różne właściwości bloku. Zapewne zapewnią również konstruktorów wygody.
Początkowa praca jest nieco większa, ale na dłuższą metę zmniejszy liczbę okropnych warunków wyścigowych i odpytywania o morderstwo baterii.
(Nie pytaj o przykład, ponieważ jest to trywialne i musieliśmy poświęcić czas na naukę podstaw celu c).
źródło
Oto sprytna sztuczka, która nie używa semafora:
To, co robisz, to czekanie przy użyciu
dispatch_sync
pustego bloku, aby Synchronicznie czekać w kolejce szeregowej wysyłki, aż do ukończenia bloku A-Synchronous.źródło
Przykładowe użycie:
źródło
Istnieje również SenTestingKitAsync, który pozwala pisać kod w ten sposób:
(Szczegółowe informacje można znaleźć w artykule objc.io. ) A ponieważ Xcode 6 zawiera
AsynchronousTesting
kategorię, wXCTest
której można pisać kod w ten sposób:źródło
Oto alternatywa dla jednego z moich testów:
źródło
NSCondition
dokumentacji dla-waitUntilDate:
„Musisz zablokować słuchawkę przed wywołaniem tej metody.” Tak-unlock
powinno być później-waitUntilDate:
.Zrobiło to dla mnie.
źródło
Czasami pomocne są również pętle Timeout. Czy możesz poczekać, aż pojawi się jakiś (być może BOOL) sygnał z asynchronicznej metody wywołania zwrotnego, ale co, jeśli nie będzie żadnej odpowiedzi i chcesz wyjść z tej pętli? Poniżej znajduje się rozwiązanie, na które najczęściej odpowiedziano powyżej, ale z dodatkiem Limit czasu.
źródło
Bardzo prymitywne rozwiązanie problemu:
źródło
Swift 4:
Użyj
synchronousRemoteObjectProxyWithErrorHandler
zamiastremoteObjectProxy
podczas tworzenia obiektu zdalnego. Koniec z semaforem.Poniższy przykład zwróci wersję otrzymaną od proxy. Bez
synchronousRemoteObjectProxyWithErrorHandler
tego nastąpi awaria (próba dostępu do niedostępnej pamięci):źródło
Muszę poczekać, aż UIWebView zostanie załadowany, zanim uruchomiłem moją metodę. Udało mi się to wykonać, wykonując kontrole gotowości UIWebView w głównym wątku za pomocą GCD w połączeniu z metodami semaforów wspomnianymi w tym wątku. Ostateczny kod wygląda następująco:
źródło