


Viele Sprachen wie Kotlin, Go, JavaScript, Python, Rost, C#, C ++ und andere haben bereits Coroutinenunterstützung, die die Implementierung von Async/Warte -Muster ermöglichen. Diese Funktion wird in Swift noch nicht unterstützt, dies kann jedoch durch einen Framework verbessert werden, ohne dass die Sprache geändert werden muss.
Die asynchrone Programmierung ist normalerweise mit Rückrufen verbunden. Es ist ziemlich bequem, bis zu viele von ihnen sind und sie beginnen, zu nisten. Dann heißt es eine Pyramide aus Schicksals oder sogar Rückrufhölle .
Ein weiteres Problem der asynchronen Programmierung ist die Fehlerbehandlung , da der natürliche Fehlerbehebungsmechanismus von Swift nicht verwendet werden kann.
Es gibt viele andere Frameworks, die es einfach machen, asynchrone Code wie Combine, RxSwift, Promisekit usw. zu verwenden. Sie verwenden andere Ansätze, die einige Nachteile haben:
Das Async/Ause-Muster ist eine Alternative, mit der eine asynchrone, nicht blockierende Funktion auf eine Weise strukturiert werden kann, die einer gewöhnlichen synchronen Funktion ähnlich ist.
Es ist bereits in anderen Programmiersprachen gut etabliert und ist eine Entwicklung in der asynchronen Programmierung. Die Implementierung dieses Musters ist dank Coroutinen möglich.
Schauen wir uns das Beispiel an, wobei Coroutine darin await() die ihn ausnutzt () und wieder aufnimmt, wenn das Ergebnis verfügbar ist, ohne den Faden zu blockieren.
//executes coroutine on the main thread
DispatchQueue . main . startCoroutine {
//extension that returns CoFuture<(data: Data, response: URLResponse)>
let dataFuture = URLSession . shared . dataTaskFuture ( for : imageURL )
//await CoFuture result that suspends coroutine and doesn't block the thread
let data : Data = try dataFuture . await ( ) . data
//create UIImage from the data
guard let image = UIImage ( data : data ) else { return }
//execute heavy task on global queue and await the result without blocking the thread
let thumbnail : UIImage = try DispatchQueue . global ( ) . await { image . makeThumbnail ( ) }
//set image in UIImageView on the main thread
self . imageView . image = thumbnail
}API -Dokumentation
Eine Coroutine ist eine Berechnung, die zu einem späteren Zeitpunkt suspendiert und wieder aufgenommen werden kann, ohne einen Faden zu blockieren. Coroutinen bauen auf regelmäßigen Funktionen auf und können auf jedem Scheduler ausgeführt werden, um während der Ausführung zwischen ihnen zu wechseln.
Das Coroutines -API -Design ist so minimalistisch wie möglich. Es besteht aus dem CoroutineScheduler -Protokoll, das beschreibt, wie Coroutinen planen ( DispatchQueue entspricht es bereits) und die Coroutine -Struktur mit Nutzmethoden. Diese API reicht aus, um erstaunliche Dinge zu tun.
Das folgende Beispiel zeigt die Verwendung von await() in einer Korutine, um asynchrone Anrufe zu wickeln.
//execute coroutine on the main thread
DispatchQueue . main . startCoroutine {
//await URLSessionDataTask response without blocking the thread
let ( data , response , error ) = try Coroutine . await { callback in
URLSession . shared . dataTask ( with : url , completionHandler : callback ) . resume ( )
}
. . . use response on the main thread . . .
} So können wir NSManagedObjectContext an CoroutineScheduler anpassen, um Coroutinen darauf zu starten.
extension NSManagedObjectContext : CoroutineScheduler {
func scheduleTask ( _ task : @escaping ( ) -> Void ) {
perform ( task )
}
}
//execute coroutine on the main thread
DispatchQueue . main . startCoroutine {
let context : NSManagedObjectContext //context with privateQueueConcurrencyType
let request : NSFetchRequest < NSDictionary > //some complex request
//execute request on the context without blocking the main thread
let result : [ NSDictionary ] = try context . await { try context . fetch ( request ) }
}Eine Zukunft ist ein schreibgeschützter Inhaber für ein Ergebnis, das später erzielt wird, und das Versprechen ist der Anbieter dieses Ergebnisses. Sie repräsentieren den eventuellen Abschluss oder Misserfolg eines asynchronen Betriebs.
Der Futures and Versprechen -Ansatz selbst ist zu einem Industrie -Standart geworden. Es ist ein bequemer Mechanismus, um den asynchronen Code zu synchronisieren. Aber zusammen mit Coroutinen wird der asynchrone Code auf die nächste Ebene verwendet und ist Teil des asynchronisierten/wartenden Musters geworden. Wenn Coroutinen ein Skelett sind, dann sind Futures und Versprechen seine Muskeln.
Futures und Versprechen werden durch die entsprechende CoFuture und ihre CoPromise -Unterklasse dargestellt.
//wraps some async func with CoFuture
func makeIntFuture ( ) -> CoFuture < Int > {
let promise = CoPromise < Int > ( )
someAsyncFunc { int in
promise . success ( int )
}
return promise
} Es ermöglicht es, mehrere Aufgaben parallel zu starten und später mit await() zu synchronisieren.
//create CoFuture<Int> that takes 2 sec. from the example above
let future1 : CoFuture < Int > = makeIntFuture ( )
//execute coroutine on the global queue and returns CoFuture<Int> with future result
let future2 : CoFuture < Int > = DispatchQueue . global ( ) . coroutineFuture {
try Coroutine . delay ( . seconds ( 3 ) ) //some work that takes 3 sec.
return 6
}
//execute coroutine on the main thread
DispatchQueue . main . startCoroutine {
let sum : Int = try future1 . await ( ) + future2 . await ( ) //will await for 3 sec.
self . label . text = " Sum is ( sum ) "
} Es ist sehr einfach, CoFuture S in eine neue zu verwandeln oder zu komponieren.
let array : [ CoFuture < Int > ]
//create new CoFuture<Int> with sum of future results
let sum = CoFuture { try array . reduce ( 0 ) { try $0 + $1 . await ( ) } }Futures und Versprechen bieten eine bequeme Möglichkeit, einen einzelnen Wert zwischen Coroutinen zu übertragen. Kanäle bieten eine Möglichkeit, einen Wertestrom zu übertragen. Konzeptionell ähnelt ein Kanal einer Warteschlange, die es ermöglicht, eine Coroutine auf Empfang zu suspendieren, wenn er leer ist, oder beim Senden, wenn es voll ist.
Dieser nicht blockierende Primitive wird in Sprachen wie Go und Kotlin häufig verwendet, und es ist ein weiteres Instrument, das die Arbeit mit Coroutinen verbessert.
Verwenden Sie zum Erstellen von Kanälen die CoChannel -Klasse.
//create a channel with a buffer which can store only one element
let channel = CoChannel < Int > ( capacity : 1 )
DispatchQueue . global ( ) . startCoroutine {
for i in 0 ..< 100 {
//imitate some work
try Coroutine . delay ( . seconds ( 1 ) )
//sends a value to the channel and suspends coroutine if its buffer is full
try channel . awaitSend ( i )
}
//close channel when all values are sent
channel . close ( )
}
DispatchQueue . global ( ) . startCoroutine {
//receives values until closed and suspends a coroutine if it's empty
for i in channel . makeIterator ( ) {
print ( " Receive " , i )
}
print ( " Done " )
} Alle gestarteten Coroutines, CoFuture und CoChannel S müssen normalerweise nicht verwiesen werden. Sie sind nach ihrer Ausführung deeinitiert. Aber oft müssen sie früher abgeschlossen werden, wenn sie nicht mehr benötigt werden. Zu diesem Zweck haben CoFuture und CoChannel Methoden zum Stornieren.
CoScope erleichtert es, den Lebenszyklus dieser Objekte zu verwalten. Es ermöglicht Ihnen, schwache Referenzen auf sie zu behalten und gegebenenfalls abzusagen oder auf Deinit.
Sie können Coroutinen, CoFuture , CoChannel und andere CoCancellable zu CoScope hinzufügen, um sie abzubrechen, wenn sie nicht mehr benötigt werden oder auf Deinit.
class ViewController : UIViewController {
let scope = CoScope ( ) //will cancel all objects on `cancel()` or deinit
func performSomeWork ( ) {
//create new `CoChannel` and add to `CoScope`
let channel = makeSomeChannel ( ) . added ( to : scope )
//execute coroutine and add to `CoScope`
DispatchQueue . main . startCoroutine ( in : scope ) { [ weak self ] in
for item in channel . makeIterator ( ) {
try self ? . performSomeWork ( with : item )
}
}
}
func performSomeWork ( with item : Item ) throws {
//create new `CoFuture` and add to `CoScope`
let future = makeSomeFuture ( item ) . added ( to : scope )
let result = try future . await ( )
. . . do some work using result . . .
}
}