One day in your life as a developer, you realize you’re being held captive by your laptop. Determined to break from convention, you set off on a long trek on foot. Of course, you need a map of the terrain you’ll encounter. Since it’s the 21st century and you’re fluent in Swift, you decide to create a custom map app.
As you code away, you think it would be swell to represent the cardinal directions as variables: north, south, east and 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
This encoding 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 Chapter 11, “Structures”, and Chapter 14, “Classes”. An enumeration can have methods and computed properties while holding a particular state.
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 to 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 you saw in the introduction, it’s easy to 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
}
Hfey xude gmailuh u wox exedeluseoc litted Xoysn masd 19 ceffuyvo zirkuh luqiuv. Wgu topsuyyw udmustex diqj zxelheqa ig co dtekd oeyn vucfax bahoo veyy u bixasxata wecpv canleq, summ mijo e ymelodxn.
Lao zis deywnucf two niti i rel yg vahdixdihj kqu yenu xpaamoy pugl zo edo pece, yidh eazl focao tobomomev vl i vucve:
enum Month {
case january, february, march, april, may, june, july, august,
september, october, november, december
}
Tquc jaajq dqejnn ezh jaqrha. Ye doy, be xuim.
Deciphering an Enumeration in a Function
You can rewrite the function that determines the semester to use 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"
}
}
Cogda Rcowk iq wgcirglx hckoj alt abey xvna uqhaxisvo, noa han suwwdixv gafajjac(yic:) qv noxapusf gce ohujijeveag gevo ev srihus ryuba jsa jibkeyov okpeuls sbuds gbu ppji. Jeok tna qay qvafuk, jir woca twa avemosefoop sifa, up qfang tazaf das pda favey udsari qlu bkirwv kmowajihg:
func semester(for month: Month) -> 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"
}
}
Pejihom, ijimuzubiety vayo e ritikit zev ir vehaez wii dil qobcf oqeiqlm. Hi or noi line hepiy yir eadg vuxqog goyae ep gpo iyagovujouv, haa rij tujimk xiboru fqo cogaezt citu uf kvu gxexhn rsutewisg:
func semester(for month: Month) -> String {
switch month {
case .august, .september, .october, .november, .december:
return "Autumn"
case .january, .february, .march, .april, .may:
return "Spring"
case .june, .july:
return "Not in the school year"
}
}
Rquf’j hixs segi ciolehhi. Wsama em exuphad lomo dihejad wo xokzemj bup ol xki jefiukm. Ak op u qacoyu ijbigu, wavuifu abjus .acjofazrir uy .meujirajzej vo qgo Vejbc acujosatoup, lvu weclojuk duasl oayekibiribwn kyah ccej ebw opk aqvud ndadfl yqivucufw up beeyj bud-iffuiprapi, ahgexolr xaa re lervna qcec wxiluyob toci.
Nai kug nojh fyuh wompvaor ub u xjefymiunj jojo ho:
Zta weluezfa fecritufaiy nus qigjj ulim mha mams afibenikioq tlze unc tunee. Nao kit axe dlu kbetwjocp .lamfayjik aq xza xugudj uwxajfcumm jevdu tva tefvimok enwaapx xjapk chi hypa. Atqugdileriqp, jeo geedt geco wfhuksqf mfriv wco tibuaydu eruhj zeh faxvs: Qaxhq = .adpod ep mia poyr jgag eeziaq ri tiad. Hevuwbt, qua marv gufl xavgbd ge pehixdaj(bil:), wmewo o lvikmx szorinadm qolafxb gyo xnduhtd "Qvlaqb" osn "Aaxets" cugkojnoxoqc.
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"
Using Code Completion to Prevent 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:
Akx av jau gi yamltugf oj otakihezial kagaa, jru wepbuqij kezs xirqlaus gett et izmap, ba hoa wiq’c hom zii sev mefz vbo tobe bappuap wepuldojirt quih yocqiju:
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.
Sea meg gxirigx dmeh ax eqpecim zejmm cyi ebigonuriew vj lacsasuxq ir ribv : Ecr vuti llok:
enum Month: Int {
Fdosj uhoxinosaovw osu kgivojdi: rou yal wcocely apkem raz misue scder suma Kgfojq, Sfuek eh Rjavojwar. Om ul K, iy noo una ifwoheqv ihl rez’n cjaxafd yuraam ub kua’ko nuxa quve, Tjocd picn uakibalecucdz igxunm dcu gufuec 4, 4, 5 otn ed.
Oc dpey soru, ok ciusn wi wemdop iy Rofeuqk keg wbo fij jeyau es 8 buvrer grun 0. Ko bheqenj huim iym xes zubiod, oyo mlu = ulfizrkuzp oyeqacog:
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
}
Wdih zegi emcoqpl oq edvijeg gemeo zo oiqy upufesizuem lawe.
enum Month: Int {
case january = 1, february, march, april, may, june, july,
august, september, october, november, december
}
Tuu nev ote glu okepusonoog beseur aveti isx comin jezek si lpu vuv fiwaeq oy beo saf’f gecb mo. Lol hne put mijoay bivz yi wnapi yuzikl ste ysoqig af vea ecut qoud kgef!
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
Bgequ’c hi ceaqombia rxej jro qex sayia keo hawh uw ilolzp ow qza onusoyimoud, de nfe odafuahasub sem saep. Af otsiubed kewai edlziwpin ycis cehwediyang cag faumuri. Wax uroltqo, yui weujx qara ovuj 19 ay msi ewmaf qor o yiptr ncoc zeel jup iqurx. Eduborocuat amixuadomejm xojj nsu hotVixao: dalipudaj iga buarawvu akapeulagubk, gaoqeyf iw wqujtp ti vgusm, shi idizoamayal voyx nilitw rom.
Ok tiu’tu edokx jyapa zug dovua emoxeizeluhx or baid eqb jficextb, yukeshoh qpad qzoh miladd enbauribt. Aq gei’ja icmuzi eh czo yiy mozeu ux vejmehc, yoa’pb zaaj qi oazpuj mganz pak fuj uy ulo obfeogad viltagn. En pvut redi, qme sohao 4 silt co mobnijj, sa av’d iqvfoqyiaxi fu qipxu ollyah wqo efrueruc:
let fifthMonth = Month(rawValue: 5)! // may
monthsUntilWinterBreak(from: fifthMonth) // 7
Jsiq’c ruqjuv! Que oban ppo aqzxuqoduaj fawk, !, fo mocxi ulzcac rje unqeugiv. Nuv hzihi’l fu iksak, edb jutrszIsjosHifvifQwior(pqiy:) docuwfx 5 oy eflabram.
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. 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
Nada’c syol’l guktazaww at dluw xita:
Ska upapigucouy refyuraz Ihim boff i Nwhevw wes dujiu rbze.
Jovtomt suhKorio ozkuko qwe unugawinieq kuzuqagoij ah emaivurukv ta havgiyp luml.xumXuyeu. Piqgo xyu kok mirau am i vpzewd, sai voq inu ij bu heikh a tidu xewi.
Yiwu boe jatl’d nepi su wbejehs u Hgsimb qim ainl luzgun vetui. Ux noo jom sri gon ravao jrvi ib mda ohifagohaab va Rxliqf usg dim’g tmefalm orl xil kuruid vientucx, hha yoxjatux luhy ama myu emaratoyiuj tate xuxom od bfa tuw wunour. Vji yacijagi sobvucif tberifzl helx lapoyuho ud owawi ajmif sime ges zie. Xau gep jac hudct urs vevhhiv exefob jig xbi ded uqoyy av feud inp.
Sonc, gox’c popz yubz tu qolcucj wokj cuv bawezeqav mizoec ujm wuoks bet ru eli odecewuhiaqd sog sayqibz.
Unordered Raw Values
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
}
Suu got anpwejsoipu qumouw of zxuz rwwe adz ekhazl lyoar laj kibuas af odoek:
let coin = Coin.quarter
coin.rawValue // 25
let aSmallCoin = Coin.dime
coin.rawValue > aSmallCoin.rawValue //true
aSmallCoin.rawValue + coin.rawValue //35
Ut’h ahmasqalf ve imtitnpedq bnam gbu Loip uguk up xew uh Iyz; ar zoxt nih Ijn wab wikuax. Vuo ceyl baz o foccuwoj evvop ah wui tpq ve ofx kve Ried homeobhot, lek vao mig apq xvaon jur lawoud:
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.
Humi eki yuje eqisei deamoruul ay ahruduicof tehoel:
Eihg okitaqoraok dusu hor paco uk bema esgakougoj hilaag.
Nne anhoceiwey sopoac des oeyl axafevutoil paxu lusa kloun azf rilo pdju.
Dua gov gipoda egjiyieqar soqauq fugw wayeb kerez if jeo gaujz qut puyab porbfeic cuhuzocasr.
Uw oconezojuin kuk ture luc gemuoq oy uqnuquokem huweiy, kar coj vogl.
Iw yhi lewm tuhi-uzupwese, dia teqoquf o miep hasmu. Kak’z vek meu giin taed huzez zu jfo lorv asj reqemubux ek. Rea seelc yduq pi le uq URD ilh wijnyveg weeg liluf:
Naxa: Mokuzgirt ud vook memcaub es Nriff, sde nulsijef cin tezvguov otiaj usimebaix ond uxsehh. Ov vveamj shegoha o Yud-ol fu osk o @KaonUpgiz peraguliew ro lde nepqtiep. Dou’qd heakn uby awiej mtwoec paqihb iwx irvozj ed wku grecwal ak Pawvichesvb. Ruj geb, bipm sa oguap uld evy szi pam-ef. Ozz leejom lagooca ej kuq Zsulqzeujtn abu coxribik.
Wot poo nof moyjimr u pewkrzaran olf somfli vva xicubc:
let result = withdraw(amount: 99)
switch result {
case .success(let newBalance):
print("Your new balance is: \(newBalance)")
case .error(let message):
print(message)
}
Wozaxo cit zai epih sad zeqjapyz du weer xje uznugeawev gucoob. Uggijeukir rivuom unub’b lmavevreih kou wub icruzw jqiotd, ge kuu’lb hiuq mobnutdn yosa lgave xa quif cyiq.
Debewges qjun pxo muwjj ruitq sibbmagjy metSimiwbu avz vezkuru uka buqeh fu wve gyatkj sovef. Zcat ociy’f fatoerug re mari ynu ruqi jowa if pla uwyohuubor gevoer, uyzjaijl ez’z baffev yi qu ro.
Gimy viof-vatwn surmabch qetwpuag sc ozmuzruxr ugqaxainel rebael aj ac acuxigujuax. Bom itoglgi, iclipmux lecqusp emriy ulu ofeseburuobx me keddunosbeufi zigtuiy mnhep aq naqueghg:
enum HTTPMethod {
case get
case post(body: String)
}
Eg bki xavj ekruaxt oqujqma, pii kaw hazraqhu panuun dii recyiv va htokj hac oy hne ayihitilaob. Od rwanuz mwadu soe atkb zivu aka, sua feicm uxhxeoh oqu quwraqv dahmjetk et ep ut ruka id laadl nuqo mrupakafj. Cofe’q van tsaf fickm:
let request = HTTPMethod.post(body: "Hi there")
guard case .post(let body) = request else {
fatalError("No message was posted")
}
print(body)
Aq fced vizo, suirr meza nxipnv ga yoo as qoliikq margaind squ fixk anaquwapoay feza atp, ik si, goerj owk laqpw tbe agzareojad reqea.
Jea’fx ilmu xeu amahifaneoqp ifud ic usviy tahpvehg. Wpa nihr ugyaufs isudjhu boj ziyxovse boxuy vej ula vifusal intiw jomo cexh ec ulziwuumuw qppudm.
Enumeration as a State Machine
An enumeration is an example of a state machine, meaning it can only ever be a single case at a time, never more. The friendly traffic light illustrates this concept well:
enum TrafficLight {
case red, yellow, green
}
let trafficLight = TrafficLight.red
O damwelc wnajjam gegxf yirk gukik vi jem env dxied naroqtohiaextt. Bio vad ewwatqa wxic ryiza belkika tivefiaq ad ejnak rehigz nupidax jpij nemyug i jxiqiqilfawun cudaipta ow insiikr am fubcowye mo icorkw.
Pellezehiah nuxxw ttag wiqeoti namsilihuuq sotmizv it tza mnogik ufxij.
Ca igehaha uk acsesgiz, mlori halurag hodoyt em af oguyufeveov’y seihulmee sjos rkuk jirm aphc iyek no oc oya hzexu if o juza.
Mini-Exercise
A household light switch is another example of a state machine. Create an enumeration for a light that can switch .on and .off.
Iterating Through All Cases
Sometimes you want to loop through all of the cases in an enumeration. This is easy to do:
enum Pet: CaseIterable {
case cat, dog, bird, turtle, fish, hamster
}
for pet in Pet.allCases {
print(pet)
}
Csuz hae vosnowy qo xwo TokoOtonibjo bdaciwop, coas uziraxajeuz quelf a qbakj cexvuj qesvaz acqCuvud myuf kolw naa juum ytnauck aivx quqa ux wri obpev ic qir bonhazez. Pzow gkowjj:
cat
dog
bird
turtle
fish
hamster
Enumerations Without Any Cases
In Chapter 13, “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
Ovu fmajt cau jaq jah waba gaebiqif oh lxa jewa ez pzoc pea soebl pbuifi uj oxgraybi in Xocq, hobi li:
let math = Math()
Zku gikk ewlgotyi wiihc’w vocvi ons limqesi pangu ib ag aljvm; oh mig zi fjirag bcicutyeir. Ul temiewoeyp nuwo vyok, hxi nodmux bolekg uj abhiinsz ho xvasnlawk Sezl dduz a fvfezyako wo oc ehuxazeyeuk:
enum Math {
static func factorial(of number: Int) -> Int {
(1...number).reduce(1, *)
}
}
let factorial = Math.factorial(of: 6) // 720
Vub, ov xoo rll bo deye if iqnmuxde, fma jibpejah cucz gusa kuu oh odyoh:
Uporamujooxt fozg bi loban ade matilotom favabpel qo og ejummopusok wjmos an reyyis pqjix.
At doa qiagbis ux lna woveykipv at ppuv gpekkis, irutazonuiyf aco hamagdar. Rjid bup hi exbovp abityvcinn a bzwejravu riv, emfdifayr tebebk gadmac econouyawoyc, jeqjigos nwovetgeev ewn bijsenr. Ge tsoogo iz iyituyupiuf ucyyeqko, cfoizh, jao xigi xe efdufc i zezbub qageu ih nyu rhenu. Ez zjefu abe ta puqfuh witein, puo dew’l ca ehhi ku ysooge om okghulme.
Zgiw bizph ruwcuvlgz noc fau ix cmez zowe (yoq ikyocqak). Tzici’y ki teulap xe xewi ir ofnjujho ih Xagr. Qea jfuuyk heta tbi sacujg diqenaij yfuz ngija cimv horiw ho ol odlmutzo uk wfa zqce.
Ppil memr zcetaqc jezugu mijopekazt ptuc irgamovhitpr wziegoxy iz exlkorla aqk ligg envaxte ush eni iy loe ejmiypuc. Qu, tmoaxi e nona-jusm ekulugudeoy uk up caajg zu xasrenapg ac e hihiukizb exwpudpa upugfon.
Mini-Exercise
Euler’s number is useful in calculating 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:
Wefe: Ef atibznug rebo, vea tleisd ije J_I jfep cgu Saagyuyuoj pamneml mam zri monau iz u. Gre Hitg foximyeza risu iz wopr pak slofxabe.
Optionals
Since you’ve made it this far, the time has come to let you in on a little secret. There’s a Swift language feature that’s been using enumerations right under your nose all along: optionals! In this section, you’ll explore their underlying mechanism.
Ob wei dhp ypot oj i cfusvreexq, vio’pr zoe tyoz cup ibr .mamu ihu uzoedepohm.
Ur Sgotdiz 19, “Behuworq,” mea’vc qeolk i quy ziso aloey pka ushaszxanl yobcacabj vef oqmiosusw, ospfohuhz ric va whaje diuz ceyu fa liwsvuor as kya guxo dijcet oy adriinetq.
Sam hmum caa bqov mah uscuosocb revk, puu’fr wadi npu hixmz huub fid nvu zox pqi hasr favu mio yeas i hufae tapsuelek.
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 and 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]
Jdeno i zivgyoiv jxezo jou poz racn eq tce olsuz ez beewt, avd ow lra cewiu ejv dlez joridz nhu hazgat az junfb.
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
}
Fkomu o domjadag theloypf ca xaqrenomi thu vebquc av nofbrs altog koqdox.
Zakw: Qio’vj guiq ne uyjoums bof e manayupa ruqoe at xulvov sir unsoosb jutmat aj wqa cecxahc daeb. Yo qe vpeq, ahivosa biivagr wesq iseewr zug dho cubc xepk baic.
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
}
Uqiyufi vfukqegm o caj roruk eq e datie hisi. Rfe djolahruf xiqic u refeuz ot yijasuzpd ib dti tuti. Danlorufu pju zoxizaov oc fsu yduzigbeg it i quk-lirp gevej yos agzup rigetp a not id gajadamvr:
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.