11

Chcę owinąć API asynchronicznej, który wygląda tak:jak owinąć asynchronicznej metody, która przyjmuje blok i skręcić go synchronicznych w Objective C

[someObject completeTaskWithCompletionHandler:^(NSString *result) { 

}]; 

w sposób synchroniczny, że mogę zadzwonić tak

NSString *result = [someObject completeTaskSynchronously]; 

Jak to zrobić? Zrobiłem trochę czytania doc i wyszukiwarka Google, a próba użycia „dispatch_semaphore” zrobić próbują osiągnąć to tak:

-(NSString *) completeTaskSynchronously { 
    __block NSString *returnResult; 
    self.semaphore = dispatch_semaphore_create(0); 
    [self completeTaskWithCompletionHandler:^(NSString *result) { 
     resultResult = result; 
     dispatch_semaphore_signal(self.semaphore); 
    }]; 

    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); 
    return resultResult; 
} 

Ale to nie robi wydaje się działać, to w zasadzie tylko zatrzymać przy dispatch_semaphore_wait. Wykonanie nigdy nie dociera do wewnętrznego bloku, który wykonuje sygnał. Ktoś ma przykład kodu, jak to zrobić? Podejrzewam, że blok musi być na innym wątku, innym niż główny wątek? Załóżmy też, że nie mam dostępu do kodu źródłowego za metodą asynchroniczną. Dzięki!

+4

Jeśli handler ukończenie jest wykonywany w tym samym wątku, który wywołuje dispatch_semaphore_wait ty rzeczywiście impasie wątek ponieważ blok Zakończenie nie mogą być wykonywane aż do wyjścia gwintu z czekania. Czy próbujesz to zrobić w głównym wątku? Lepiej nie blokować głównego wątku przez długi czas, ponieważ musi on stale wysyłać wiadomości. – yurish

+0

Jeśli, jak podejrzewa @urish, obsługa została umieszczona w kolejce do głównego wątku wysyłki, nie możesz czekać. Musisz skonstruować swój przepływ kodu jako maszynę stanu i zrobić wszystko, co trzeba, z wynikiem w procedurze uzupełniania. –

+2

Nie ma ogólnego sposobu, aby to zrobić. Jak powiedzieli inni, jeśli jakaś część asynchronicznego zadania działa poprzez umieszczanie zdarzeń w pętli uruchamiania, zawsze będziesz zaklinowany. Co tak naprawdę * starasz się osiągnąć? Może istnieje inny sposób na uporządkowanie kodu. – JeremyP

Odpowiedz

-2

Możesz spróbować użyć NSOperations, aby robić to asynchronicznie.

8

blokuje główną kolejkę w twoim przykładzie. Można wysyłką zadanie asynchronicznej do innej kolejki:

__block NSString *returnResult; 
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL); 
dispatch_async(queue,^{ 
    result = [someObject completeTaskSynchronously]; 
}); 

też użyć innego systemu, jak NSRunLoop:

__block finished = NO; 
    [self completeTaskWithCompletionHandler:^(NSString *result) { 
     resultResult = result; 
     finished = YES; 
    }]; 
    while (!finished) { 
     // wait 1 second for the task to finish (you are wasting time waiting here) 
     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]]; 
    } 
6

Korzystanie NSRunLoop jest najłatwiejsze do zrobienia.

__block NSString* result = nil; 
[self completeTaskWithCompletionHandler:^(NSString *resultstring) { 
    result = resultstring; 
}]; 
while (!result) { 
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 
} 
+0

Tak, myślę, że to działa dobrze. Znalazłem również klasę MTTestSemaphore na githubie, która zawiera to, co jest bardziej semaforowe. Dzięki. – kawingkelvin

+0

Zastanawiam się, jak dobrze to wpływa na normalne przetwarzanie zdarzeń? Rzeczy takie jak transakcje Core Animation itp. – yurish

+3

To nie działa dobrze. Pętla uruchamiania wymaga przetworzenia zdarzeń w celu zwrócenia. Jeśli nie ma żadnych zdarzeń, powróci tylko w "odległej przyszłości", czyli * nigdy *. Ponadto dostęp do wskaźnika 'result' nie jest bezpieczny dla wątków. Nie ma gwarancji, że wskaźnik nie jest równy zeru I w pełni zainicjowany. I (być może) kompilator może mieć możliwość optymalizacji dostępu do lokalizacji pamięci i utrzymywania wskaźnika w rejestrze, który nigdy nie zostanie zaktualizowany. – CouchDeveloper

1

Myślę, że lepszym rozwiązaniem będzie NSRunLoop, jak podano poniżej. To proste i działa dobrze.

- (NSString *)getValue { 

    __block BOOL _completed = NO; 
    __block NSString *mValue = nil; 


    [self doSomethingWithCompletionHandler:^(id __nullable value, NSError * __nullable error) { 
     mValue = value; 
     _completed = YES; 
    }]; 

    while (!_completed) { 
     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 
    } 

    return mValue; 
} 
Powiązane problemy