Writing

← Writing

Async Await, maar dan in Swift

March 20, 2021

Deze week heb ik mij verdiept in asynchronous programming. Waarom? Omdat Swift niet heel lang geleden een proposal heeft goedgekeurd waarin Async Await wordt toegevoegd in Swift. Maar voordat ik daarover ga schrijven eerst iets over Async Await.

Wat is het?

Normaal gesproken runt je code stukje voor stukje. Stel je hebt een functie waarin een gebruiker zich registreert. Na het invullen van een formulier, drukt de gebruiker op verstuur. Dit activeert een functie die de gebruiker toevoegt aan de database, een email stuurt en de gebruiker inlogt en doorstuurt naar een dashboard. Als je deze functie niet asynchroon maakt, moet je wachten tot dit allemaal gebeurt is. Terwijl het voor de gebruiker niet echt nodig is om te wachten op het versturen van een email. Hoe langer de functies hoe hoger de wachttijd, deze wachttijd wil je natuurlijk laag houden. Daarom programmeren we tegenwoordig veel asynchroon.

In Swift

Met de goedkeuring van proposal SE-0296 is er een goede stap gezet in het toevoegen van Async/Await in Swift. Waar we voorheen veel moesten werken met closures om een vorm van async te krijgen. Kunnen we binnenkort veel eenvoudiger asynchroon programmeren. Dit heeft als grootste voordeel het leesbaar houden van je code. Maar hoe werkt dit?

Omdat Async/Await nog niet officieel gereleased is, zijn de meeste functies ook nog niet klaar hiervoor. Daarom gebruik ik withUnsafeThrowingContinuation.

Stel ik wil een artikel van het internet halen waar ik vervolgens de titel en text uit haal. Deze titel en text stop ik vervolgens in een Artikel model die ik terugstuur naar een viewmodel. Om te beginnen moet ik hiervoor een Api call uitvoeren om de data op te halen.

func getArticle(from url: String) async throws -> String {
    guard let url = URL(string: URL) else {
        return "Bad url"
    }

    return try await withUnsafeThrowingContinuation { continuation in
        URLSession.shared.downloadTask(with: url) { (data, response, error) in
            guard let data = data else {
                continuation.resume(throwing: "Bad URL")
                return
            }
            if let html = try? String(contentsOf: data) {
                continuation.resume(returning: html)
            }
        }
        .resume()
    }
}

Zoals je ziet download deze code een pagina van het internet en pakt het de html die vervolgens teruggestuurd wordt naar de caller. Door gebruikt te maken van withUnsafeThrowingContinuation kan je je functie asynchroon maken. En met de continuation return je je data zodra het er klaar voor is.

Nu de api call gemaakt is, kan de titel en text gevonden worden. Voor het gemak laat ik dit even opzij. Het werkt namelijk net als de API calls. Voor nu ga ik door naar het complete plaatje, het ophalen, verwerken en terugsturen van de data.

func process(url: String) async throws -> Article {
    let htmlData = try await getArticle(from: url)
    let articleText = try await extractText(from: htmlData)
    let articleTitle = try await extractTitle(from: htmlData)

    return Article(
        title: title,
        text: text
    )
}

Zoals je hier ziet, haalt de code eerst de html data, deze kan vervolgens gebruikt worden voor het vinden van de titel en text. De laatste 2 functies kunnen tegelijk gebeuren, dit versneld het proces extreem.

Maar hoe gebruik je deze functie dan uiteindelijk. Dit doe je met behulp van de @asyncHandler hiermee gebruik je de async function en store je je data.

var articles = [Article]()

@asyncHandler func getNewArticleAsync(from url: String) {
    do {
        let article = try await process(url: url)
        DispatchQueue.main.async {
            self.articles.append(article)
        }
    }
    catch {
        print(error.localizedDescription)
    }
}

Na het uitvoeren van deze functie komt er een nieuw artikel in je articles variable die je vervolgens kan gebruiken in je UI. En zo heb je gebruik gemaakt van Async Await in Swift.

The future is bright

Met deze eerste stap in de richting van asynchroon programmeren is het lezen van de code een stuk beter geworden. Je hoeft niet meer completionHandler achter completionHandler te gebruiken waardoor je code een soort kerstboom wordt. Een grote stap mag je het zeker noemen, maar er komen nog veel meer toffe dingen aan. De Swift Concurrency Roadmap laat heel mooi zien wat Swift nog meer van plan is. Er valt dus nog genoeg te leren.