7 H7: Netwerk en async/await
De klok draait, de kleuren kloppen — maar de prijzen zijn nog nep. Het wordt tijd om echte data op te halen van het internet. In dit hoofdstuk leer je hoe je een netwerkaanroep doet in Swift, hoe je JSON vertaalt naar jouw eigen structs, en hoe je async/await gebruikt zodat je app niet bevriest tijdens het wachten.
7.1 Wat gaan we bouwen?
De EnergyAPIClient: een struct die prijzen ophaalt van de easyEnergy API en ze teruggeeft als een lijst van EnergyPrice-objecten. Na dit hoofdstuk haalt de app echte stroomprijzen op.

7.2 Playground-voorbeeld
import Foundation
// Maak een eenvoudige struct die we willen decoderen
struct Artikel: Codable {
let id: Int
let title: String
let completed: Bool
}
// Haal één nep-taak op van een test-API
func haalArtikelOp() async throws -> Artikel {
let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(Artikel.self, from: data)
}
// Start de aanroep
Task {
do {
let artikel = try await haalArtikelOp()
print("Titel: \(artikel.title)")
print("Klaar: \(artikel.completed)")
} catch {
print("Fout: \(error)")
}
}Dit haalt een JSON-object op van het internet en zet het om naar een Swift-struct. Dat is in drie regels geregeld.
7.3 Concept uitgelegd
7.3.1 Async/await: wachten zonder te blokkeren
Stel je voor dat je een bibliotheekboek bestelt. Je geeft de bestelling door aan de balie, en daarna ga je gewoon zitten lezen terwijl de medewerker het boek gaat zoeken. Je wacht, maar je zit niet te staren naar de balie — je doet ondertussen iets nuttigs.
Zo werkt async/await. De app doet een aanroep naar het internet (await), en terwijl het netwerk bezig is, blijft de interface gewoon werken. Je kunt scrollen, klikken, dingen doen. Zodra het antwoord er is, gaat de code verder.
// Zonder async/await: de app bevriest tijdens het wachten
// (dit werkt niet meer in modern Swift)
// Met async/await: de app blijft reageren
let data = try await URLSession.shared.data(from: url)7.3.2 JSON: data als tekst
Servers sturen data terug als JSON — een soort tekst die eruitziet als een woordenboek:
{
"hour": 8,
"price": 0.09,
"date": "2026-03-22T08:00:00Z"
}Swift kan dit automatisch vertalen naar een struct, mits je struct Codable is en de veldnamen overeenkomen.
7.3.3 throws en do/catch: omgaan met fouten
Een netwerkaanroep kan mislukken: geen internet, server down, ongeldige data. Functies die kunnen mislukken zijn gemarkeerd met throws. Je roept ze aan met try, en je vangt fouten op met do/catch:
do {
let prijzen = try await haalPrijzenOp()
// succes
} catch {
print("Fout: \(error)")
// toon een melding aan de gebruiker
}7.4 Code schrijven
Stap 1: het API-model
Maak APIModels.swift. Hier staan structs die precies overeenkomen met wat de API teruggeeft:
import Foundation
// Bron: EnergyClock/APIModels.swift
// Decodeermodel voor de easyEnergy API
struct EasyEnergyItem: Codable {
let Timestamp: Date
let TariffReturn: Double
}De veldnamen Timestamp en TariffReturn moeten exact overeenkomen met de JSON die de API stuurt.
Stap 2: de API-client
Maak EnergyAPIClient.swift:
import Foundation
// Bron: EnergyClock/EnergyAPIClient.swift
// Haalt energieprijzen op van de easyEnergy API
struct EnergyAPIClient {
func laadHuidigePrijzen() async throws -> [EnergyPrice] {
let kalender = Calendar.current
let vandaag = kalender.startOfDay(for: Date())
let overmorgen = kalender.date(byAdding: .day, value: 2, to: vandaag) ?? vandaag
let isoStijl = Date.ISO8601FormatStyle()
.year().month().day()
.time(includingFractionalSeconds: true)
.timeZone(separator: .colon)
var components = URLComponents()
components.scheme = "https"
components.host = "mijn.easyenergy.com"
components.path = "/nl/api/tariff/getapxtariffs"
components.queryItems = [
URLQueryItem(name: "startTimestamp", value: vandaag.formatted(isoStijl)),
URLQueryItem(name: "endTimestamp", value: overmorgen.formatted(isoStijl)),
]
guard let url = components.url else {
throw APIFout.ongedigeURL("easyEnergy URL kon niet worden gebouwd")
}
let (data, _) = try await URLSession.shared.data(from: url)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let items = try decoder.decode([EasyEnergyItem].self, from: data)
return converteerEasyEnergy(items)
}
// Zet easyEnergy-items om naar EnergyPrice-objecten
private func converteerEasyEnergy(_ items: [EasyEnergyItem]) -> [EnergyPrice] {
let kalender = Calendar.current
return items.map { item in
let startVanUur = kalender.date(
from: kalender.dateComponents([.year, .month, .day, .hour], from: item.Timestamp)
) ?? item.Timestamp
let uur = kalender.component(.hour, from: startVanUur)
return EnergyPrice(hour: uur, price: item.TariffReturn, date: startVanUur)
}.sorted { $0.date < $1.date }
}
}
// Fouttypen voor API-aanroepen
// Bron: EnergyClock/EnergyAPIClient.swift
enum APIFout: Error, LocalizedError {
case ongedigeURL(String)
var errorDescription: String? {
switch self {
case .ongedigeURL(let url): return "Ongeldige URL: \(url)"
}
}
}Stap 3: de client gebruiken in een view
In de ContentView kun je de client aanroepen via .task:
struct ContentView: View {
@State private var prijzen: [EnergyPrice] = []
@State private var fout: String?
let client = EnergyAPIClient()
var body: some View {
VStack {
if let fout {
Text("Fout: \(fout)")
.foregroundStyle(.red)
} else {
Text("\(prijzen.count) prijzen geladen")
}
}
.task {
do {
prijzen = try await client.laadHuidigePrijzen()
} catch {
fout = error.localizedDescription
}
}
}
}Als jouw app URLSession.shared.data(from: url) aanroept, gebeurt er veel onder de motorkap:
- DNS-opzoeking — de domeinnaam (
mijn.easyenergy.com) wordt omgezet naar een IP-adres via het Domain Name System. - TCP-verbinding — er wordt een betrouwbare verbinding opgezet met het TCP-protocol (Transmission Control Protocol).
- TLS-handshake — de verbinding wordt versleuteld met TLS zodat niemand kan meelezen.
- HTTP-verzoek — de app stuurt een GET-verzoek met headers en URL-parameters.
- HTTP-antwoord — de server stuurt JSON terug met een statuscodes (200 = OK, 404 = niet gevonden, 500 = serverfout).
URLSession regelt dit allemaal voor je. Het is een hoge-niveau abstractie bovenop de BSD socket API die al decennialang in Unix-systemen zit.
De async/await-interface van URLSession maakt gebruik van Swift’s structured concurrency: taken hebben een duidelijke levensduur, fouten propageren netjes omhoog, en annulering werkt automatisch als de view verdwijnt.
7.5 Apple documentatie
Alles over URLSession:
developer.apple.com/documentation/foundation/urlsession
Alles over Codable en JSON:
Meer over async/await:
Zoek in de URLSession-documentatie naar data(from:). Lees de “Discussion” sectie — die legt uit hoe annulering werkt als de view verdwijnt.
7.6 Samenvatting
| Begrip | Betekenis |
|---|---|
async |
Markeert een functie die kan wachten |
await |
Wacht op het resultaat van een async functie |
throws |
Markeert een functie die een fout kan gooien |
try |
Roept een throwing functie aan |
do / catch |
Probeert code uit; vangt fouten op |
URLSession |
De netwerk-laag van Apple |
JSONDecoder |
Zet JSON-tekst om naar Swift-structs |
Codable |
Protocol dat structs decodeerbaar maakt vanuit JSON |
URLComponents |
Bouwt een URL op uit losse onderdelen |
7.7 Opdracht
Breid de EnergyAPIClient uit:
- Voeg een functie toe
laadEnergyZero()die prijzen ophaalt vanapi.energyzero.nl. De URL-structuur vind je in de broncode van de app (EnergyAPIClient.swift). - Maak een
EnergyZeroResponseenEnergyZeroPricestruct inAPIModels.swiftdie overeenkomen met de JSON van EnergyZero. - Test de nieuwe functie door hem aan te roepen vanuit een
.taskin een simpele view die het aantal ontvangen prijzen afdrukt.