3  H3: Gegevens structureren

Tot nu toe hebben we losse variabelen gebruikt: één voor een naam, één voor een leeftijd. Maar wat als je meerdere dingen bij elkaar wilt bewaren die bij hetzelfde onderwerp horen? In dit hoofdstuk leer je hoe je gegevens samenvoegt in een struct — en hoe je die gegevens kunt lezen uit een JSON-bestand.

3.1 Wat gaan we bouwen?

We bouwen het fundament van de EnergyClock app: het EnergyPrice-model. Dit is de struct die één uurprijs beschrijft — welk uur het is, wat de prijs is, en op welke dag. Straks gebruikt de hele app dit model.

// Bron: EnergyClock/EnergyPrice.swift
struct EnergyPrice: Codable, Equatable, Identifiable {
    var id: Date { date }
    let hour: Int
    let price: Double?
    let date: Date
}

3.2 Playground-voorbeeld

import Foundation

// Een struct beschrijft hoe iets eruitziet
struct Boek {
    let titel: String
    let auteur: String
    var bladzijden: Int
}

// Een instantie maken van de struct
var mijnBoek = Boek(titel: "Swift op macOS", auteur: "Bas", bladzijden: 300)

// Eigenschappen uitlezen
print(mijnBoek.titel)       // Swift op macOS
print(mijnBoek.bladzijden)  // 300

// Een var-eigenschap aanpassen
mijnBoek.bladzijden = 320
print(mijnBoek.bladzijden)  // 320

3.3 Concept uitgelegd

3.3.1 Struct: een recept

Een struct is als een recept. Het recept voor een appeltaart beschrijft wat erin gaat: appels, bloem, suiker. Maar het recept zelf is geen taart — het is alleen de beschrijving.

Als je de taart daadwerkelijk maakt, maak je een instantie van het recept. Je kunt hetzelfde recept meerdere keren gebruiken om verschillende taarten te maken.

// Het recept (de struct)
struct Persoon {
    let naam: String
    var leeftijd: Int
}

// De taart (een instantie)
var emma = Persoon(naam: "Emma", leeftijd: 9)
var lotte = Persoon(naam: "Lotte", leeftijd: 11)

print(emma.naam)    // Emma
print(lotte.naam)   // Lotte

Elke instantie heeft zijn eigen kopie van de gegevens. Als je emma.leeftijd verandert, verandert lotte.leeftijd niet mee.

3.3.2 Optionals: een doosje dat leeg kan zijn

In het echte leven zijn sommige gegevens soms gewoon niet beschikbaar. De stroomprijs van morgen weten we vanavond nog niet. In Swift gebruik je hiervoor een optional: een waarde die er kan zijn, maar ook nil kan zijn — leeg.

Je herkent een optional aan het vraagteken achter het type:

var prijsVanMorgen: Double? = nil   // nog niet bekend
var prijsVanGisteren: Double? = 0.08  // wel bekend

Als je een optional gebruikt, moet je altijd controleren of hij leeg is:

if let prijs = prijsVanMorgen {
    print("Prijs: \(prijs)")
} else {
    print("Prijs nog niet bekend")
}

Dit heet optional binding: je “opent het doosje” en kijkt of er iets in zit.

3.4 Code schrijven

We maken nu het EnergyPrice-model voor de EnergyClock. Maak een nieuw Swift-bestand aan in Xcode: File > New > File > Swift File, noem het EnergyPrice.swift.

Stap 1: de basis struct

import Foundation

// Bron: EnergyClock/EnergyPrice.swift
// Model voor energieprijs per uur
struct EnergyPrice {
    let hour: Int       // het uur (0 tot 23)
    let price: Double?  // de prijs in euro's — nil als nog niet bekend
    let date: Date      // de datum van dit uur
}

Stap 2: een hulpeigenschap toevoegen

Een computed property berekent zijn waarde op basis van andere eigenschappen:

struct EnergyPrice {
    let hour: Int
    let price: Double?
    let date: Date

    // Berekende eigenschap: is er een prijs beschikbaar?
    var isAvailable: Bool {
        price != nil
    }
}

Stap 3: een instantie maken en testen

let prijsAchtuur = EnergyPrice(hour: 8, price: 0.09, date: Date())
let prijsNegentien = EnergyPrice(hour: 19, price: nil, date: Date())

print(prijsAchtuur.isAvailable)    // true
print(prijsNegentien.isAvailable)  // false

Stap 4: Codable toevoegen

Later willen we prijzen ophalen van het internet als JSON. Door Codable toe te voegen, kan Swift dat automatisch vertalen:

struct EnergyPrice: Codable {
    let hour: Int
    let price: Double?
    let date: Date
}

Meer over JSON en Codable lees je in H7.

NoteVerdieping: value types en reference types

In Swift zijn structs value types: als je een struct kopieert, krijg je een volledig onafhankelijke kopie. Wijzig je de kopie, dan verandert het origineel niet.

var a = Boek(titel: "Swift", auteur: "Bas", bladzijden: 300)
var b = a           // b is een volledige kopie
b.bladzijden = 400  // a.bladzijden blijft 300

Classes zijn reference types: kopiëren geeft geen nieuwe instantie maar een tweede verwijzing naar hetzelfde object. Wijzig je de “kopie”, dan wijzig je het origineel.

Swift moedigt het gebruik van structs aan. Ze zijn eenvoudiger te redeneren over, veiliger in concurrent code (meerdere threads), en goedkoper qua geheugen omdat ze op de stack leven in plaats van de heap.

De keuze voor struct vs. class heeft ook gevolgen voor automatic reference counting (ARC) — het mechanisme waarmee Swift geheugen vrijgeeft. Structs worden automatisch vrijgegeven zodra ze buiten scope gaan; classes hebben ARC nodig om bij te houden wanneer ze nog ergens naar verwezen worden.

3.5 Apple documentatie

Meer over structs en properties:

docs.swift.org — Structures and Classes

Meer over optionals:

docs.swift.org — The Basics: Optionals

Open de documentatie en zoek het gedeelte “Optional Binding”. Lees de eerste twee voorbeelden — die laten precies zien hoe het if let-patroon werkt.

3.6 Samenvatting

Begrip Betekenis
struct Een mal waarmee je gegevens groepeert
Instantie Een concreet object gemaakt van een struct
let (in struct) Eigenschap die na aanmaken niet meer verandert
var (in struct) Eigenschap die later nog aangepast kan worden
Optional (?) Een waarde die er kan zijn, maar ook nil kan zijn
nil Leeg — geen waarde
if let Optional binding: controleer of een optional een waarde heeft
Computed property Een eigenschap die zijn waarde berekent
Codable Protocol dat een struct leesbaar maakt voor JSON

3.7 Opdracht

Maak een eigen struct in een Xcode Playground: Stroom. Hij heeft drie eigenschappen:

  • uur: een Int (welk uur van de dag)
  • watt: een Double? (het verbruik in watt — optioneel, want soms onbekend)
  • isSpitsuur: een Bool

Maak daarna twee instanties: één voor 8 uur ’s ochtends met een bekend verbruik, en één voor 3 uur ’s nachts zonder verbruik. Druk voor beide af of het spitsuur is en of het verbruik beschikbaar is.