Blog:

Force unwrap? Liever niet!

Published on jul 12, 2017
By Cliff / Software Engineer

Wanneer je het eerst Swift gaat gebruiken, kan het al snel gebeuren dat je fouten maakt met optionals. Dit is iets dat wij zelf ook meerdere keren gezien hebben, ook in onze eigen code. Omdat Swift steeds populairder wordt hebben wij, Cliff Wings en Tom Eestermans, besloten hier een blog over te schrijven. In deze blog zullen wij uitleggen hoe je om moet gaan met optionals in Swift. Laten we eerst eens vertellen wat optionele variabelen nou precies zijn en welke verschillende soorten er bestaan.

Wat zijn optionals en welke verschillende soorten zijn er?

Optionals gebruiken we eigenlijk altijd al. Het is namelijk niet meer dan een variabele die een
waarde of nil (geen waarde) kan bevatten. Het grote verschil is dat we ze in Swift ook
daadwerkelijk benoemen en ze anders afhandelen. Er bestaan in Swift namelijk twee soorten
optionals. Deze worden als volgt geschreven:

var optionalVariableOne : OurClass?
var optionalVariableTwo : OurClass!

Als we het over de twee variabelen hierboven hebben, zien we dat ze beiden optioneel zijn. Het
verschil zit hem in hoe de compiler hiermee omgaat en hoe je deze in code kunt afhandelen.

?

Met het gebruik van het vraagteken vertel je de compiler dat deze variabele nil kan zijn en ook op
deze manier afgehandeld moet worden. Het is niet mogelijk om variabele van deze klasse te
benaderen zonder deze op een veilige manier ‘uit te pakken’, oftewel unwrappen.

!

Met het gebruik van het uitroepteken vertel je de compiler dat deze variabele nil kan zijn, maar dat
jij zeker weet dat er een waarde in zit. Daarna mag je deze variabele dan ook gebruiken alsof deze
niet optioneel is.

Maar wat nou als er wel nil in zit? Juist, dan crasht je app runtime! Iets waar je niet op zit te
wachten als ontwikkelaar. Dit is dus niet de juiste manier voor de meeste variabele. Maar waarom
eigenlijk niet?

Hoe het niet te doen!

De verkeerde manier is, heel simpel gezegd, 95% van alles waarbij een ‘!’ wordt gebruikt.
Of het nu force unwrappen (myClass!.itsVariable) of casten (myClass as! AnotherClass) is, er is
een betere manier. Bewust het risico nemen dat je app op runtime crasht omdat het een scenario
tegenkomt waar je niet aan gedacht hebt tijdens development is iets wat je wilt voorkomen.
Hieronder een aantal voorbeelden.

Unwrapping

Wanneer een object optioneel is en je een van zijn methoden of properties wilt benaderen, zul je
deze moeten “unwrappen”. Er zijn twee manieren om dit te doen, welke ook aaneengeschakeld
kunnen worden als je jezelf in diepere lagen wil begeven.

optionalObject?.property?.name
optionalObject!.property!.name

Dus, wat is nu eigenlijk het verschil tussen deze twee? Wanneer het object dat je probeert te
unwrappen een nil waarde bevat, zal de ‘?’ schakeling simpelweg nil returnen en doorgaan. Het
resultaat van unwrappen met een ‘?’ zal hierdoor altijd een optional zijn. De ‘!’ lost dit niet op een
mooie manier op, deze zal je app laten crashen.

Casting

Het casten van een object wordt gedaan met het ‘as’ keyword. Wanneer ‘as’ gebruikt wordt
zonder een vraag- of uitroepteken zal de compiler een waarschuwing geven als dit niet mogelijk
is. Wanneer ‘as’ met een vraag- of uitroepteken gebruikt wordt zal de compiler ernaar kijken zoals
hij altijd doet in optional gevallen.

Laten we het nu eens hebben over een aantal manieren die veilig een non-optional kunnen
produceren.

Op een veilige manier optionals gebruiken

Er zijn meerdere manieren hoe je optionals op een veilig manier kan gebruiken.

Het if-statement

Je kunt gebruik maken van een if-statement. Wanneer je checkt of de optional een nil-waarde
bevat kan je veilig force unwrapping gebruiken in je code.


if myClassObject == nil {
return
}
myClassObject!.itsProperty//Safely accessible

Dit zal prima werken en is volledig functioneel, maar heeft een aantal nadelen.

  • Je checkt op een conditie die je niet wilt. Code wordt verwarrend wanneer je een aantal checks
    zoals deze hebt.
  • Om de properties of methodes van je object te kunnen benaderen, moet je het object forceunwrappen,
    wat nu precies hetgeen is wat we hier proberen te voorkomen!

En een if-let statement dan?


let myOptionalClassObject : MyClass?
...
if let unwrapped = myOptionalClassObject as? MyClass {
unwrapped.itsProperty //Safely accessible
}
unwrapped.itsProperty //Compiler error, unwrapped not known

Deze vorm van een if-statement functioneert net als ieder ander, op het feit na dat de conditie een
declaratie is. Zoals je kan zien is de ‘as?’ cast gebruikt en zal het resultaat van de cast in
‘unwrapped’, een non-optional object van hetzelfde type, opgeslagen worden. Wanneer de cast
lukt zal de body van de if betreden worden en kan unwrapped gebruikt worden. Wanneer het
mislukt zal de body niet betreden worden.
Hierdoor hebben we beide nadelen van de vorige methode niet, maar helaas wel een andere. Je
stopt de code die je wenst uit te voeren in de body van alle condities in plaats van erna. Dit lijkt
geen probleem, maar je code kan al snel verwarrend worden wanneer deze genest zit in talloze
condities die allemaal moeten slagen voor je je code kan laten runnen. Dit gaat dus ten koste van
je leesbaarheid en is geen ideale manier.

Dus hoe kunnen we onze optionals unwrappen zonder force-unwrapping, terwijl we onze
leesbaarheid behouden en we op een veilige manier kunnen checken op de condities die we
graag zien?

The way to go!

De manier om dit te doen is om eerst alle checks te doen en wanneer een van de checks niet
slaagt te returnen. Dit zorgt voor een eenvoudige en leesbare manier om te snappen welke
condities benodigd zijn voor het goed runnen van de methode.

Blijkbaar wordt dit ook wel het “Bouncer Pattern” genoemd, wat eigenlijk heel logisch is als je
erover nadenkt. Je wil de slechte gevallen al wegsturen voor ze door de deur stappen. Dit zorgt er
ook voor dat je maar over een situatie tegelijkertijd hoeft te denken.
Dit kan allemaal gedaan worden met een ‘guard-statement’. Net als een if-statement voert een
guard code uit gebaseerd op de boolean waarde van een expressie. Het verschil zit hem vooral in
het feit dat de guard zijn body, waar verplicht uit gereturned moet worden, alleen uitvoert wanneer
er niet aan de condities voldaan wordt.

var myOptionalClassObject: MyClass? = MyClass()
printItsProperty() //This will print true
func printItsProperty () {
guard let unwrapped = myOptionalClassObject else {
//In this case, myOptionalClassObject couldn't be unwrapped/casted
//unwrapped is also not known in this scope
//here you can safely handle the "crash"
return
}
print(unwrapped.itsProperty) //safely accessible
}
class MyClass {
var itsProperty = true
}

Het gebruiken van een guard lost alle 3 de eerder genoemde problemen op:

  • Je checkt voor de conditie die je wilt, in plaats van de conditie die je niet wil. Wanneer er niet
    aan de conditie voldaan wordt, wordt de else-body van de guard gerund, welke verplicht uit de
    functie returned. Wanneer er wel aan de conditie voldaan wordt zal de optionele variabele in dit
    voorbeeld automatisch unwrapped zijn binnen de scope waarin de guard gebruikt is.
  • Je checked vroeg voor de slechte gevallen, wat je functie leesbaarder en makkelijker te
    onderhouden maakt.
  • Compile-time checking of je niet per ongeluk door je failsafe heen valt wanneer er niet aan je
    condities voldaan wordt. Ook kan je, doordat je case-by-case aan het checken ben, hele
    specifieke error-logging aanhouden, waardoor debuggen een stuk aangenamer wordt.

Wij hopen dat we je hebben kunnen overtuigen van de schoonheid van het guard-statement, voor
zowel unwrapping als belangrijkere checks. Wanneer je vragen hebt, aarzel dan niet om ons een
berichtje te sturen!

Auteurs: Cliff Wings, Tom Eestermans
19 Juni 2017