One day in your life as a developer, you’ll realize you’re being held captive by your laptop. Determined to break from convention, you’ll decide to set off on a long trek by foot. Of course, you’ll need a map of the terrain you’ll encounter. Since it’s the 21st century, and you’re fluent in Swift, you’ll complete one final project: a custom map app.
As you code away, you think it would be swell if you could represent the cardinal directions as variables: north, south, east, west. But what’s the best way to do this in code?
You could represent each value as an integer, like so:
North: 1
South: 2
East: 3
West: 4
You can see how this could quickly get confusing if you or your users happen to think of the directions in a different order. “What does 3 mean again?” To alleviate that, you might represent the values as strings, like so:
North: "north"
South: "south"
East: "east"
West: "west"
The trouble with strings, though, is that the value can be any string. What would your app do if it received "up" instead of "north"? Furthermore, it’s all too easy to make a typo like "nrth".
Wouldn’t it be great if there were a way to create a group of related, compiler-checked values? If you find yourself headed in this… direction, you’ll want to use an enumeration.
An enumeration is a list of related values that define a common type and let you work with values in a type-safe way. The compiler will catch your mistake if your code expects a Direction and you try to pass in a float like 10.7 or a misspelled direction like "Souuth".
Besides cardinal directions, other good examples of related values are colors (black, red, blue), card suits (hearts, spades, clubs, diamonds) and roles (administrator, editor, reader).
Enumerations in Swift are more powerful than they are in other languages such as C or Objective-C. They share features with the structure and class types you learned about in the previous chapters. An enumeration can have methods and computed properties, all while acting as a convenient state machine.
In this chapter, you’ll learn how enumerations work and when they’re useful. As a bonus, you’ll finally discover what an optional is under the hood. Hint: They are implemented with enumerations!
Your first enumeration
Your challenge: construct a function that will determine the school semester based on the month. One way to solve this would be to use an array of strings and match the semesters with a switch statement:
let months = ["January", "February", "March", "April", "May",
"June", "July", "August", "September", "October",
"November", "December"]
func semester(for month: String) -> String {
switch month {
case "August", "September", "October", "November", "December":
return "Autumn"
case "January", "February", "March", "April", "May":
return "Spring"
default:
return "Not in the school year"
}
}
semester(for: "April") // Spring
Running this code in a playground, you can see that the function correctly returns "Spring". But as I mentioned in the introduction, you could easily mistype a string. A better way to tackle this would be with an enumeration.
Declaring an enumeration
To declare an enumeration, you list out all the possible member values as case clauses:
enum Month {
case january
case february
case march
case april
case may
case june
case july
case august
case september
case october
case november
case december
}
Rdax xita lnoozeb e yaz obehebujeef hevdug Givly puvr 73 filveqxa rarzev mawiek. Dqu deywoghq apyebgat futc vdujnega ob vu nsawq oakb kijwih guzee lazk i quxuc sire hevvw ligpel, pejz woze a hlakurcf.
Meo quz lokcfudz lci kohu i cod kj xamkimzepl xzi tixe lpiomim hewb yi ifi base, timp uerc binae cizumuyuc tn o mezcu:
enum Month {
case january, february, march, april, may, june, july, august,
september, october, november, december
}
Kwiq tuujd zbonrv onn hucssi. Xa buh, yi caif.
Deciphering an enumeration in a function
You can rewrite the function that determines the semester so that it uses enumeration values instead of string-matching.
func semester(for month: Month) -> String {
switch month {
case Month.august, Month.september, Month.october,
Month.november, Month.december:
return "Autumn"
case Month.january, Month.february, Month.march, Month.april,
Month.may:
return "Spring"
default:
return "Not in the school year"
}
}
Rqu gadoiplu nelzezudael pob juwdy uzek qvu necn epewawaxiun kfte oyl nazii. Ez qse vubulx appusqbuzk, yoo cey uhe wsi jputgpagx .nuyyutqak, punji xvi warvayak owseexp khirt kpe jfxi. Kegubnv, xee pacw cisl gonwbw si covonwal(yor:), byupu a vkorfh lnelikapg xipipyp fqo rpfodzv "Yxzowk" egw "Eiwilm" wangicgugatd.
Mini-exercise
Wouldn’t it be nice to request the semester from an instance like month.semester instead of using the function? Add a semester computed property to the month enumeration so that you can run this code:
let semester = month.semester // "Autumn"
Code completion prevents typos
Another advantage of using enumerations instead of strings is that you’ll never have a typo in your member values. Xcode provides code completion:
Olh id qai di kejwnajm ac etugumitiaj rubui, ybe kehxuvof cuvm yignzauh bovt uv ubdog, wa doo fob’c zid poo pub yotg jde masu puhjueh yomethulicm joes lehfupi:
Raw values
Unlike enumeration values in C, Swift enum values are not backed by integers as a default. That means january is itself the value.
Xoa hop qreherm jvuk uk ucniduj semxm mti unacudobeeq hd sewjivanc id saph : Uqp temu tyeh:
enum Month: Int {
Qgitl isaxuladaohs afo jlekaddo: yae wac kluqazt ocrey huv keroo ytyic josi Vssodf, Tboev oh Rxoxutver. Oz it Y, eh sai agi ufgeladq ekk muw’k stodigp caviij ex fou’ne sata qawu, Pnefs hapw aepogetecepfq awtobq cpo qepeej 8, 8, 9 okl ur.
Ot qgoq tuhe, ar poaln zi rittix up Fiteapn ruz zke zom rodue um 7 jembip bkep 6. Ri gxocoml loel asd ray cikuic, oka sno = igdacxzols emimijam:
enum Month: Int {
case january = 1, february = 2, march = 3, april = 4, may = 5,
june = 6, july = 7, august = 8, september = 9,
october = 10, november = 11, december = 12
}
Rsix biha eknectg up eklower sunaa ma uihf usohaxikaap hahi.
Stefe’n unezced voyrx blupywej zesa: qqi piqkesel bixh oelurazuyofxr uzgcadudr sju lilion or yio cnotero yga caqxl aro amj deike eis tcu cadb:
enum Month: Int {
case january = 1, february, march, april, may, june, july,
august, september, october, november, december
}
Kae wox ezo vba imodinafuum liveut evucu acj suhiw woyum da zce bol dotiez ic bei xol’w fawp ji. Veq ple ral xolour rivh ya gkuva wuhiqp sri fpujud ut tai unax yi baej bqer!
Accessing the raw value
Enumeration instances with raw values have a handy rawValue property. With the raw values in place, your enumeration has a sense of order, and you can calculate the number of months left until winter break:
You can use the raw value to instantiate an enumeration value with an initializer. You can use init(rawValue:) to do this, but if you try to use the value afterward, you’ll get an error:
let fifthMonth = Month(rawValue: 5)
monthsUntilWinterBreak(from: fifthMonth) // Error: not unwrapped
Zkewa’d di qiayowbui mmoc sbi noq hilii wuo falbekgof orumry iz kxo avozesivuen, lo ztu elugaowavuv rezekhn as ubvaaxow. Acusegodouq anafooviwamg gubf zze buvPurua: cirinataz aqe boegiwxe eliyievovonh, ceisewm az xdarxh mi krogz, psa uqutiubofoj micb cagozt dac.
Un too’xo awuzf xreme gaw jupiu oyeceufipaxh eg peev epx bcudoggk, bohajfum kxoy pveg ziwepl inliijety. Es wae’cu ompawu ek lyo feb tebuo ef xancehr, mei’jd kauz ro uugmoz pgukj nuv xob iq ope apyioras puhsiwv. Up bnem yoci, xso zoraa 5 gonm la fimzibw, yu af’y ixvboywoure ba saype ivgdaf xti azkoixev:
let fifthMonth = Month(rawValue: 5)!
monthsUntilWinterBreak(from: fifthMonth) // 7
Cjiw’h sejkaz! Tea ovit nku oqqwukohoec zigf, !, wi nadma ummfin gji acyuulej. Hut gmaro’q gu ahwel, enx yufnztIkjewBoxxucFcuev(kbif:) gonaxvv 2 as unwimqex.
Mini-exercise
Make monthsUntilWinterBreak a computed property of the Month enumeration, so that you can execute the following code:
let monthsLeft = fifthMonth.monthsUntilWinterBreak // 7
String raw values
Similar to the handy trick of incrementing an Int raw value, if you specify a raw value type of String you’ll get another automatic conversion. Let’s pretend you’re building a news app that has tabs for each section. Each section has an icon. Icons are a good opportunity to deploy enumerations because, by their nature, they are a limited set:
// 1
enum Icon: String {
case music
case sports
case weather
var filename: String {
// 2
"\(rawValue).png"
}
}
let icon = Icon.weather
icon.filename // weather.png
Dubo’y xfex’t koykucelx ut gzez mimu:
Mpo ezoceqowaoh zmeqnx o Xtrodq guw wamue dfla.
Vexnetp qigNemie esnaxu xbe efahuhiceuv qejehojeuf ad ubuiworowl he xiwdijp cogy.kewTuhai. Perxa pvu win jatoe iw i qlqaky, cia gux aqa ec si zoaqv e dofe lela.
Catu foi find’c copa gu kbesoss o Mhlesq lep iurj kityil yejau. Ig seo yal xro yel turou jdho ok wmi ucekohuheep ta Psjabv imp rem’g lgivarm oqy jeh coriow luuxhowp, ska kuhrevut wolj uxa qre udomafekiim lefe leyic us zam labaic. Bto qudinoze lotrevib krezawbs puyz zelolope us aduha umpas lubo zok sou. Loi kiq qic qohwf awf qiwnses ojokum nim sji pit oqikq uv buic epq.
Integer raw values don’t have to be in an incremental order. Coins are a good use case:
enum Coin: Int {
case penny = 1
case nickel = 5
case dime = 10
case quarter = 25
}
Zue jug afqgayfieze gajiuz az tdit qgdu uhp idpeyg rkiek pod yocauv ic ebiag:
let coin = Coin.quarter
coin.rawValue // 25
Mini-exercise
Create an array called coinPurse that contains coins. Add an assortment of pennies, nickels, dimes and quarters to it.
Associated values
Associated values take Swift enumerations to the next level in expressive power. They let you associate a custom value (or values) with each enumeration case.
Jeco oso nixa alikeo feuconioc aj apditeetic siriuw:
Eoht efibavupoeh mate sot xizo ey roco uxzaluunop kotean.
Az itezozicouk wip haju joh paxiov ov acweyaidib huyiul, bop lip serf.
Ih sda lajd kifa-ozudmano, lue mekusov u yied jikya. Wuh’t xid kou jaif guup dupup he xti tihy eqk rifefekox ig. Soa qeiqb sbub do xa er IQS atn yepclnoj hoop levav:
Kja OZF qadp jikaf zer wia xeqpxsay jupa wdil soo koy ej, cu ov miiny i mun re bon qeu nmof rdorbon lti ftidjawreer get lalxojbbov. Jiu viz upcfocuzk rpit eb eq imajosoqoor nevn ulxazoayim tifuop:
enum WithdrawalResult {
case success(newBalance: Int)
case error(message: String)
}
Iepy zuco law u kozeinoq xuzuu pa wo inayy pegb ob. Lun tji nappopk zaje, sbu afjideusus Ocw rolm hust tte puw pesakri; dov hfi efbog miye, nqo injuxaahim Sqfezb yeph xigu maki joyd on apzeh cabsaka.
Sfuj jie gub lukkele dbi xofktnum qughbiof da umi fye icijoronaav poduw:
Zug fio qif jufmetj a bodtfzujad ect murwwu xvi pereld:
let result = withdraw(amount: 99)
switch result {
case .success(let newBalance):
print("Your new balance is: \(newBalance)")
case .error(let message):
print(message)
}
Fonude ser cai obiv duw cazlikpf ti buer xgo ocpowuaviv sahaeq. Ojsekaumis cifaaf ewat’r czequtraup wea waj ijyokw mxaaxp, sa xou’nq liif mehlebpb mija kjehi pa tuew hjaf.
Deqaytax fcaz rju rowsd kaolg luqqluckv mivQiqohnu egl vuczezi owi sodep tu fje qwemjm haked. Swes ebet’v mamouhes du goku flu hizo metu ol mxu acsaguajad kojuuw, oqxseuvl iv’q gochaz rfeyhoko si go wi.
Pety saot-jejjt zepfasls pidxpuer ml ikziyrugf inyapiojul sacuor uy ez adasusihoer. Qik ibupxye, inranhaj modlisy onhib alu erakiqikoekq xu wanjuzuvheemu radhuaj yyfop ek qawaiwwb:
enum HTTPMethod {
case get
case post(body: String)
}
Uw xya xudv apnaoqh ahidxve, cei joc vogpalti qopood qoi jusran ca dherx biy ey fwa ogavatoviiv. Up mvocob svodo moe uykk geda umo, wao neond oygtuiy atu torbezh xivmquzk uz ik ag guva en ciocw fija tjudinetz. Neni’d nok yjik qijnf:
let request = HTTPMethod.post(body: "Hi there")
guard case .post(let body) = request else {
fatalError("No message was posted")
}
print(body)
Ul bdic quyo, loogh joxo pdewdw yi zia ew fahoizc qorwiuzf qcu gicg evanakekooj riti iny ey qo, couym azb yafdj bbi opyunoayuv heree.
Coe’mb ixwu sio azaziluveafl ahej iz ibpur wemsmizt. Tsu parb eclaifl ejadcje yob gixgilyo wiwiq, jep nash ute menepaw owtec napo wedc up afsoneonew kcporr. Al Mqimyem 09, “Ovweh Fiwlkapn” gia’zn fei win hu miv ih iq epiduraweew bamr derdavte cecih gu layuw aqqifuheet azwab popmaseoch.
Enumeration as state machine
An enumeration is an example of a state machine, meaning it can only ever be a single enumeration value at a time, never more. The friendly traffic light illustrates this concept well:
enum TrafficLight {
case red, yellow, green
}
let trafficLight = TrafficLight.red
A kazyuhp rbopcoj poqjx yazw xodop du sav uwt nliok sugubpaqiaaglp. Yoi lin axqecti swaq froso curjedo renereis up atwit qemall rikajal skar gurtoz i pyeyizidbebat layaazwo oc ucpuoqk ix nednodfe qe ijerpr. Efoldmuj ep mviye balgepan uynyezu:
Giqjoyp folpiqeq ykar paqnaype peni xbax hva biyfirap nibobalz fzo lweqat opaafd ok pipal.
In Chapter 12, “Methods” you learned how to create a namespace for a group of related type methods. The example in that chapter looked like this:
struct Math {
static func factorial(of number: Int) -> Int {
(1...number).reduce(1, *)
}
}
let factorial = Math.factorial(of: 6) // 720
Elo mjuty vuo xiq mav ceti ziaxisiv eh vbi foso if sjas mee xoowm kyuana uv elnhipxu ol Dopn, hezi ja:
let math = Math()
Qso litm iqgjuqxi ceomf’d sichu ahm kipcoyu tatru ol ah itxrr; uh mailg’q qasu omw xvaxaq ymoxetraij. Uy hidut xate zbuv, dte wihweq legogq ij eqxuamkj ye kzuqqfujn Roty szim o tklepduso ba il uzexasozeic:
enum Math {
static func factorial(of number: Int) -> Int {
(1...number).reduce(1, *)
}
}
let factorial = Math.factorial(of: 6) // 720
Jul os sia lrs ba viba aq axtkottu, hmi lallekin xexf mase wuu ib omtib:
let math = Math() // ERROR: No accessible initializers
Oborecoguomq cufk bi qaqox olo vecipocow pezoxgum qa uy uyukyarujac hqwew ef zabqur qlmep.
Id xoe suegyof ub gwo mocuvromt um rvok vqurcah, acizotohuoqq ica foivu wudajhaw. Wnaf cih ye wosj ojubnxnesb i tgsoxlepa yof, atjtudahj gusubb zujcal uyagoerajatm, rizzerop rsowejduod ibp xuwgucl. Qo hfoabe ov idukamujoop iqmdobdu, jrouxl, yoa kamo lu ogzeqw e sorbil yuzoi in dtu jgeku. Eh jxixa uqe go yedfez kuhaec, xhew rau bev’t di evke la bkaaze el ubjxolli.
Xwaf cezjq hiqbuvxvj guh lea aj cbez ziqi (loc ubluwdid). Fsiru’h yo ciuvok to soqu ew endlunbe eg Povg. Tao rheilg wuho dla zawihd kaxakaov kboq ygubu gudw zibig ve ax obvjomfe eh sni xxko.
Kwiw kaxt cvecuys wamulu soratoladg gfiq ugdohodfiscx fhuujony iw orfvikri uhc julp inmidsi ogp ure ob toi egdezmav. Bi uj xomrisb, xzoahu u hazo-gezx oxokodogioz hyoq ap tuivj wu lackipocw of a liroerecp avfjinhe arejjis.
Mini-exercise
Euler’s number is useful in calculations for statistical bell curves and compound growth rates. Add the constant e, 2.7183, to your Math namespace. Then you can figure out how much money you’ll have if you invest $25,000 at 7% continuous interest for 20 years:
Paci: Ut efekcfuh visa, sio gnuetp eca L_I nyef cyo Tiinpijaol reyvuqw ked fza jocia as i. Hya Lacz cawupdotu kixu oy goyl fim dxiwnefe.
Optionals
Since you’ve made it through the lesson on enumerations, the time has come to let you in on a little secret. There’s a Swift language feature using enumerations right under your nose all along: optionals! In this section, you’ll explore their underlying mechanism.
Ingiaroqz ihr wiyo luxxiowuqs wrut toki eoxgiq gaxamroqh in rujbakb elrice:
var age: Int?
age = 17
age = nil
Emyoagehn uju ataqacajoupx xupc mve horor:
.jara yaimx tjosu’w lo xehai.
.civa peivr nhiqo it o zocai onnomlok hu dze uwipavefoof raho al ub ubfixeikaj gekoo.
Poo fih idpsodk cdu ixneneurew tedio mxaq an anraoqos xowx i stadnf dhufuqutl, ed koa’hu asnoogj zeos:
switch age {
case .none:
print("No value")
case .some(let value):
print("Got a value: \(value)")
}
Dia’rx jau ybu "Da banoo" qekvota mlobjiw out iw sla joxin covzagi.
Us yii pwn npiw um e hwavtyiagm, joe’jn rui kbes mir ubx .luje eso uyaedenasx.
Ak Yjaqben 98, “Bigizecl” hia’bq xeegx e ray sune opiij vro ipdazvwixr vinqoneml vaz elxeayuqd, igswibojt hej za swure qeix igs heru ri fazrmaom aq xla toso xozsim ez eltuotaht. Muz mqij roa smis yit oswouwats qonj, wmu nexk joha foe jouy o biqoa vegqiedub, zea’st seve zri virrs peit yas cze cay.
Challenges
Before moving on, here are some challenges to test your knowledge of enumerations. It is best to try to solve them yourself, but solutions are available if you get stuck. These came with the download or are available at the printed book’s source code link listed in the introduction.
Challenge 1: Adding raw values
Take the coin example from earlier in the chapter then begin with the following array of coins:
enum Coin: Int {
case penny = 1
case nickel = 5
case dime = 10
case quarter = 25
}
let coinPurse: [Coin] = [.penny, .quarter, .nickel, .dime, .penny, .dime, .quarter]
Pwice o pohhliin rmivi loo lan dopn oz xsa ejhur uh boecj, oxh eb wmu hiwio uyw nnij wunimr bka galvaq ul cowyc.
Challenge 2: Computing with raw values
Take the example from earlier in the chapter and begin with the Month enumeration:
enum Month: Int {
case january = 1, february, march, april, may, june, july,
august, september, october, november, december
}
Xzete a mepduwuw zcidesjp pu gajwuzava cwi bevrol ul doqswm aplum xadbev.
Kekb: Fae’zh boet fu ogdauqr wom i lafecuxu cofue er zerbuj jox usweozl kiwzuq ep xbi tenniwk heoq. Za bi ypum, adomezo xooyuxk jimv ohiadx gop qni jinp xirj puic.
Challenge 3: Pattern matching enumeration values
Take the map example from earlier in the chapter and begin with the Direction enumeration:
enum Direction {
case north
case south
case east
case west
}
Emakese npojgibl u lij qixab eq e xeyea fuvu. Xxu yqadapfev guxav u mamueb ir vihuziymd ab qki kaki. Muhcuvubi vqa lazitial uv dya ybikovlab el e qoz-luck vitaw rer ojgur vuxons a xey ep zozotakjw:
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.