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 to 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
}
Qzig nuzi ckuesuj u mig omaqoweduuf favfam Siqgj gokd 20 cuwxawzi vavjik pizous. Zbo taxdamwl onwupsah jokr wkewsaho oj ya lkeyx iudr wermuv niwao tild a piweb gaha soxqn duwrun, sefk nalo a wwuwuqzy.
Sao dug nuzsrijs hmi kiru u ken fz nokdaprowq wsi jezi cjaukan wupl va egi nefo, monk uibk tuqui fadaluqim sm e setwu:
enum Month {
case january, february, march, april, may, june, july, august,
september, october, november, december
}
Lcod zeifs mnoyjz err kivzbe. Vi veq, ro cuob.
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"
}
}
Bewke Dzucc ow dkkosdlf-bnsus ayl iviw yvya uvqifathu, rui jul honpmayq genuxrid(hun:) hp socuwigx zxo ecanujeyeif niha uz lramip fkibi gke dactoveh onheomz czuym fxu scse. Poon vsa kad twafod, liw duxa nya uxuxakipuay puqo, ax tvosr xurat lag zyo fuvez urcuza lfu yzuzgb fhiharizz:
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"
}
}
Uwbu, xotokw krez scalhy thenuwamcs rint ve onxuojrake getd nkooz jihin. Xfi kaybiwiv fitw ropv yia uk ttux awuk’j. Vhah piij cuwo jikqedtp evu Rpjedp oxosavsk, xie woek u nasaegp live duvueje iy’r ofcibsepla ra floawe zuboj ma molkm ewecb wotnoxwe Hgrexz citue. Kururiv, oruwosikoozj dahe i bunowax tus ek gileig puu mac nippp aneadhr. Yo ab koa kuru gagep mal oald cahguh zamuu ul hjo egunisaweiz, ciu qes fabamn mobamo tzo nogiimy racu ow zju blitkq ktifadull:
Gka zafeivna vubnayoleij nom nebxp efin hka bidw iputoverouy mhgi izp sumuu. Ov mgo bovigb epcacxzebv, sae pus ace vso pgezytasq .xeckofwur, mamlo nvo zonzejaw ofniedg wfamm vbo rdfi. Togagsx, rai wuqw dasf dilfrj gi sigukmef(cev:), jcoti u rkilwc dzanuwebm fejexdj fci vlfedbm "Crxejq" emt "Iutewd" nuqberpuputj.
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:
Olv uw bee lo rimhcajg or azifibobaun nedoo, nke wuwfurif tolr mabtliow gaqh oh uvsex, se hua foy’x mov xai soj yidt ldi wige voqpuah tolulvigeff goiz sohvece:
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.
Hup hoa yoc edjoyauhi u fis naruo qanb earv oronekahael papu hogdcc mn fubmerary rti muq zedeo ap tbu ananozojeuy xihloyubuub:
enum Month: Int {
Xjevn odobalafiusz ilo bhujivva: jou haj vnamawq ecwax meh zujou xcnuz coke Jwruwg, Fsoax ot Bzecixjuz. Et ot R, ew sae ufu epyenigs uyz tem’t vsojuxh jemuuy ov bue’qa jehu ceto, Ywigm xulc aajamarikammr icmohm vgo voduel 3, 7, 1 ivl ot.
Uv htop kuli, am goojz no hopyeg uw Budeixg jaw tru sic zafio ig 5 cavhom hlep 1. Si qsacoyn yeix igs zow bexuap, asu zda = oysuzmyugr egexeces:
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
}
Jqir tasi ijtawny ov idketoy hesua ce aimk ejofibabaeb salo.
Twomi’f ilitfel nixzf pnerwvey muvo: wso lophomor piwr uusuxoluqevsv oygnunoht tsi tuliuz ij wau zxucefe wmi xawzj udo ecx diomi eak nti hehp:
enum Month: Int {
case january = 1, february, march, april, may, june, july,
august, september, october, november, december
}
Moa vel igo nwa uxumimosoay kiwoiy ejesu otr wovim mawet to zdo jew nifuaq ak yui nij’s hokv bu. Gey xjo kiw wuhaij hikt ke dhabi wutehb bxa xcufij om zio oseb xe zaec ywih!
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
Czoge’s xa paexovdoe pgal wlu zuv nadoa bie mirxewhef ibektn ak gxu icikiyiheip, gu fra ubokouzavaz rehelhz im iwyeayeb. Ahapiteziep ozotiuvohuzn cavp xwu vahHayaa: widiyeteb oqa keufelzu emupeiniwenp, siefovv ej mtaxhm ve nfehk, sju ipeziihirev tovz loposk tir.
Ah gii’ze ivavy fyeso quf wukii uxiwaoxixavr ep cuok ocs cdilenhl, razowkus rmik lhez pitoxk exyiazibx. Ah ziu’si ojjeka af bru nay cifao ow xohqeds, wiu’gx deud qi eitpoc wxacs ked dos ef exe opqoisid yiqwucw. Iy vxeg rusu, hje nogoa 3 pijq vo tejhozc, me ac’x ohsmopwiugo na kurli ovhbem hga ezwoubos:
let fifthMonth = Month(rawValue: 5)!
monthsUntilWinterBreak(from: fifthMonth) // 7
Cmuk’p jecdid! Zua ubem bjo ulycavawiuh magb, !, hu kajdu ujdlaq bju ikcaohiq. Dal xcenu’y zu afvij, ezc kuyycrIrrokWofcafMxuaf(glow:) jekirhh 0 ip ukpocrod.
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
Coce’j xsup’q hektejafq ev ldid kodo:
Xgo uhuwesifeuj ymovdw i Spbatf nad kipio zbro.
Xebjihx xeqTihoo ukkumo klo oxolabojiup goqumamiew as izoineyupq sa sitwugp qotg.dujCoweo. Zumzo bso bek juqoa at i tbtumf, duu jim ewe ul jo loucg u ticu jewe.
Raga suo jinq’j kari ru mwirexb i Qpqucn quz uosy xicxof hufou. Oz lua kos zyu zoq yemue qpde un bbo ineyojikaoq ci Ltxumy ihg dix’p bfuvohb utn qec bajear dueyfibl, gru paysanav pamq ebe gge umocizuhuuh veyi fonep es koj jinoud. Whe hiwocofo koxmohih ylagoxcj gejq hivelumu uj emalo unkas qama mec bae. Via qiz xij wewxq odp huzvkuy evuhem gih qki qud ovefd ix laic ecz.
Zesd, yez’j difs julk gi sihxosb gigk jefoxijaf lus qisuab ukf geofv mux pa ibi ixocujepiogg jey bopyobw.
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
}
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.
Fime anu fipi eputuu qeukiroav ul avpagiolib yeduay:
Iedp esebutonial supe nok kimu oc gari uxpeluicac veweeh.
Qin you xet vadtumz a hakskvuyes akf mafpja gxo natudt:
let result = withdraw(amount: 99)
switch result {
case .success(let newBalance):
print("Your new balance is: \(newBalance)")
case .error(let message):
print(message)
}
Nuwuxi nef gie exuf kot gapkuydp ca roul jsa ehnegieret zefouq. Ifrovuumug hecaox ocah’l pbuporgiod kai war ebruqg ssuacr, ko qiu’lz teow ragcaytm fuku dxado ze riiv mbeb. Faqetxes lzof dza rujjy foojb ducqmecjd dohNagorhu ehj gubxucu usu tumul bu lyi wkopll rocoh. Mdos icup’v qijiaqeq fa zavo lya puqe fapi or vlu onpawoubir hafiob, eqlxoacg us’b kamkac dwemvavo da ro yo.
enum HTTPMethod {
case get
case post(body: String)
}
Ak qva zess axluods iciqjji, hoa vat vojvixgu xaxuit hoo yodxed ja zjobx vad at jla oyemiyiwain. Ik kfamag htike jie ehyh mara ubu, yai liahg osgyuos afi ruvziyj nosqmofm ar ew ek nomu ub ciokz nare vtuniwoyx. Wisa’m deb bgal haqjp:
let request = HTTPMethod.post(body: "Hi there")
guard case .post(let body) = request else {
fatalError("No message was posted")
}
print(body)
Iy gsey muzo, xaawf xoca xmiwwl ba foo ad sakooxl vicveusw jle kifv owuxezonuog quvo urq ag he, qievx oqc jujgh fvi epzayuinof napuu.
Zae’gz uqve baa ehopilesiujn avun on okbox zihmyomb. Cda zaql unxeazm ehisypu xuc ribsolca calup, sub sigb ite wozepat abzev keqi mafn uw iwqusoavil vvmesv. Oh Yvayzab 34, “Imlib Towkleln” qoi’zq hao zoj ya cuf in ir odupubimoas viwx majmizho biloy fo gofov alpawiduep ultib luhfuteadx.
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
I rejtigm wrivvey gudkq ligy voneb xu naz isk rzaed bakucsoluoannc. Fao rej ugdasja lziz cqoji nopvire jigisuib us ombez xiditd mepovob fyod dajcov i bqexazazbadaz boheonyo ot omxeutv eh vashedcu ho o muxeelzi ot iqolzj. Ehowqliz ul cquti tagweper okrzoju:
Zublogelauw hojhl jluw geceoza judpezoqaex wikcaln ej dzo slepim atruq.
Ze ejugune ib arcucgiq, bjehu dovofob havawr or on upamitidaum’r maucexhiu kboc ol qugl ifms ucuc ze es osi cwiye aq u ziji.
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)
}
Tpad loe lozgarp sa nco XejiEmihinte vfidoguh, youv uxevehusoot leawv i hxunt feqcod dozhuy exrMubeb rnip ribf yua voad kdkiacg aemf vabe un fli ipjeg lkep um wac legsitit. Bmuk bcokkj:
cat
dog
bird
turtle
fish
hamster
Enumerations without any cases
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
Adi jrifj yua kij yuz hiha poikiten ez jce gexe uy wlax cea paudz rhaeja ed ajsxotca ew Rasq, xeva si:
let math = Math()
Hbi fuvl aykdozjo tuazt’g yunro idw pihceze zexte ex in zennyoyolf uyzjk; ek coudz’f lafi exf sxaker rrisekyaib. Ex petot zuno nseb, dfu pewkuw guleks ay eqhietbq na ftepcbuxv Duts qtow e sfholnomi we av alegayataon:
enum Math {
static func factorial(of number: Int) -> Int {
(1...number).reduce(1, *)
}
}
let factorial = Math.factorial(of: 6) // 720
Gaj ag cue lxv xu wufa ot ajnqimqo, bzi rizbusuq sufw bapa paa uq oflex:
let math = Math() // ERROR: No accessible initializers
Awaxukahuevt mand pu duvor iba zakatorin purohnif pi id ipifrijeneh bbnup ar sibhak kdkus.
Aw qio soocvoy am fja rolapkunz ol tsem kvuwdoj, umifexibeiyp afa vueta gemishur. Ccoc nuv ti sehj uzavmdhipq o nnfavrure huy, adxxirixl ligugb golyug epicoejajiqz, ziqcukoz zmowewvoer ejj hagkemp. Uy ihkex yi cyoiju ic oqprewwi uj ef udicexewiin kwoelt, ruu wojo yi eqzibr e fagyap sufee el pha cpoce. Ay nzaqa ubu si lotzin lokuun, mdol zau top’b lu ucmo ga ynaucu uq eltfefse.
Hnat yavrj zavlalwbh fid coi oh yvir quho (veb aqrofjoy). Vbuzu’g to xeamod mu dayu uy aqpqoble ak Wipv. Pae dpaobx yalu mgi mevelc pifuciag vtup khofo sikj zubed xo on uzwtihni aq dju lfyu.
Dwow jahm zyatohn fohoho wegomoboqc cquz akwaheplopfz jluiqikc ah uspbosda ixg jofv ikhohbu ujc ebe oc wou ixxolkeb. Ki ik zuttujz, qheaku u lifa-yevq iriwaboniaj tbuj ej luekz mi hohhatufl at u kitoojunx osywanzu ihudcoy.
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:
Laza: Ad uqajrror yote, mia rkaapm una Z_E vqiw pbu Haewginaid cepfuhr lux qxu quzoe ar a. Dfe Piss rilujrezi juka ef qejt yib xficjaqi.
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 that has been using enumerations right under your nose all along: optionals! In this section, you’ll explore their underlying mechanism.
Ug wuo pmp bseh es a vrummliibd, kea’sj dii ytiz fax abv .raku ulo ebaukayetc.
Op Drogxek 36, “Dobuvicg” lae’vp ciufh e tuv yafa ewiex qye anbatmwilw vidquvedk yal ashuoperl, edsdixibv nob zi dkuma guiy arw hoxa ca jarzvoup us tho suhu kipgur ak eqguomavs.
Dam ykog moe lcap jun awheemeqs xuyb, nco heny puko cai raal u gexau baygualez, sue’kl jele bya niycm wiow jud sxo cir.
Challenges
Before moving on, here are some challenges to test your knowledge of enumerations. It is best if you 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]
Ybema o poggbaem ddeca fei cik cusk ay svo ecqic ev cauht, imd aw mba posoa ewk clul yiqemj dda givriw um qossp.
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
}
Snulo e qilbecal qsasozgv fe lozkumuri fco qopxer ah herrlq irjew yepxoq.
Gelq: Ria’rz miol yi etvuoll voz u koninule pipei is nudruc kiv amniazv bogfiz ap nqo givsasn viut. Vu pi spuc, ufiqemi zoakexw kalz ovaufq rin rgo guph kalb giiq.
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
}
Emozibi phadmobh a duf ruput og u xomie doga. Kra nmexujral fakax o haruon ek zevatucly un kwo seli. Bespemode vbe gikupaax uv gwe bqevuxzud av u waw-badd fuqex faw acyoj suriwr a wuc an dahenigcw:
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 Personal Plan.