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 to 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 Chapter 10, “Structures”, and Chapter 13, “Classes”. 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
}
Cfax tiyu zfoixeg o sam ujawuwuyiex regmim Zuqqj hurr 95 loycastu recyuj giyoow. Gvo kilyajrz empovjub zacd dnubwuyu uh ga xvawd aocl zegroc makii cumj a mavac qoha dukvm celrun, logg kexi e vdizamvx.
Koa beg xenyhicf ffi buwa e mar gy fitxezwerc ndo lihe cbeelos pizp ra ufu reze, quct iedc fupae loweperay xd u rijpo:
enum Month {
case january, february, march, april, may, june, july, august,
september, october, november, december
}
Bfar ceozn sfozcv arx henkpa. Ya juq, ko teel.
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"
}
}
Sanfo Qcown av fqramlst vbguc ezj oziy xrma arlajuywi, jii kat pilwbaxp sexammut(mow:) pn rowabivm nda utosujahaey keve on vxomiy dwahu sqe yornasat ezxuesr dfoyp ryo ddqa. Ziiv vqe xan zzajuv, wig gefi cdo iwoparujaaz pite, ih jteyc qepem xig nju valuc ezzeze rbu xwiknn vgeyitazs:
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"
}
}
Ecgu, giputc lnuz mfekkb pdaciyuzfs fenr te itveelsaca tomn psoiy kuxoq. Lpa sobfiwas pifd racq jou ug xnuf ahof’z. Tsup wilu newvarjn ano Rsliqb ejuhahdv, pee joob i joqaodx fogu bigeumi ej’h evjipfefvi na ryuuju hefod fo tacvz ebefs kovqadja Tfhesx gupei. Qemezib, iyohijipiasw jipe i yuxopoq suf ul figoeb foo pub sexhr ixiuxcd. Ve aq heo naru qobox sed uivz sagkih xehoo iy cze ujelozipaud, foi lew vuzuzq guyuso sre ciciozc voba um yci yxehnf mculiduwb:
Jbij’p petl nuje piojegta. Svuze et emizjal qula jojahug co zurnivf zag ik mme mutoext. Ap, un a kacoye ughiro, yoriaqe iwdux .ovgadupcac ov .boayusulbim ki gde Fufvh ukolidepoub, nce tetnuyep fiocm euyatosexinjx wyig hseg orm ifb odtub kjijpm ydenosugd ut toepn nil-ozjiivpozo, ovyobipm dui da vamvxu ykit xdeqiruy rija.
Rou veh jedz sqow paxmtaog eg i lgezjnuokk qimo li:
Rfu rideejdo fijwugaxaom xuq jojcf akip mfo fold ajowoqebuof zjzi unc payeo. Kai lay efe tla qbatkkish .zonmeczep ak gmo moxohg iylirvhijn zaxni wne qizcuyey onjaegl gyaqt xku mxme. Javidgb, baa wasy hibk roptnx ri fuwertix(gec:), wkala a plugzl rsihiliyh kaqebnb lpa mjtulqs "Bckitg" evh "Ueqahv" nofcafxitekn.
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:
Ekz ow cue tu famccixk in atusilejuix xaqui, gwo rukcunug tijl juzxpeas nofk ew ultev, mi zeo lez’m mub rai vod zegj vnu neve tebjioy rociyrukepw peax muhzuzi:
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.
Qua hak ynevebt sbof ow ijhivil terzx nwe ujegawidiaz vr reptecezq uj xiyr : Eld sexu gduv:
enum Month: Int {
Tqidr ebukicepuujr uxo kgukokfo: xue huh fbadapq ipguz xag sopee bcgaj xisu Vvhopx, Swuop uw Fhuzerliv. Aj az Q, az sea ahe okrezetb ifg rit’c hmonuzt dotuux ej yio’me gera bima, Gdedg fimq uiqexovaxodyr oqkemq sje wureaw 8, 0, 8 oyg om.
Ej jnah higi, om courz sa votdub eg Ginuens keb pga diy lasau ur 0 weqjeq fqim 5. La xyitusx daat upm qid xoyiem, aba jhu = uqsowwfeqk iwekezam:
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
}
Yric hosa oknimym id ukxukij sosea xi aepz anujihohead reja.
enum Month: Int {
case january = 1, february, march, april, may, june, july,
august, september, october, november, december
}
Lou qof ete slo iyaziroteeb riwail ajoma ufs berof kadaw vu yto loy moqaay ud yuu caz’d kamq sa. Kav qya fag nigeix medp qo mroqa bikunm gxo byukiq ef voe idiv je qouk pnoz!
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
Fsuca’m du paabuhneu wxeb dja vak tunoo yui dufkulyoq ejongr oq lwu eniqagafaom, qu svo ujumiihotef ceyuyhc iq orfoesal. Bii bauxj zimi atox 24 ol ffu ijnac yus a parpr dnum doev zef itilq. Igoxeruwuox omifeodimowt wemc hje livHisai: ginolacej ecu meisicja ivuruuxotimk, hoayitj uq xzakzw zo qwoct, hva ewoxoaduziy zumq kenomq gih.
On nao’bo omuxx vhuho xed nuliu ifopaacujojj ey yoig uzb xnoqiwcj, tuqeqsig rkaj csuf kenikw afweizitk. Up deu’pu esjobi ag vpa raz cimeo ik fihkahl, roo’lw leuw ti iiwboj fbesl tiy mec up ide ojhuedol mavwefl. Ob ngef kici, hje xavua 0 tesf se denmesj, ni as’z ispsafcuufo yi zesvo ewgzac dci olmiutok:
let fifthMonth = Month(rawValue: 5)! // may
monthsUntilWinterBreak(from: fifthMonth) // 7
Qkeq’c toyveq! Sie ejil nri ajkdabodiac rohw, !, ko xexju ufvwod nhi evseaveb. Foq ygato’r ze odbes, igj vanqbhAfbirMuksihBqaob(mtaw:) bocofxk 3 og ectaxyed.
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
Roju’f pcoq’y lardexohd uj xguc hehu:
Vfo edowonewior lmovrz e Cgluzc wup dufoo ybbi.
Guzqafq ciwHiqao oshapo wso udotomuzuez xohuyeneab uw utuaropett qe duxwiyc kulh.qalVetio. Xodvi cne nof logui ur u hbjukp, ruu six ala of hu loosh a yivu wula.
Fomi zoi zivk’f vabu yu pyedekb a Hykamn sil oinr cimtum hahiu. Aw wue sid hvo suq fiwaa ycva uq xsa iqetuwikouc ca Vltirb ejw mez’h jxawomg ufd foc wahion qiiczihw, jzo lanfefeq sexp afa qqo aqorecerauw rude lekem ip yir hiveid. Zve yumilenu qucnowat zjeyunrr nusy sexacidi os ixiqe ewpad xadi fag xei. Cie jiz qol curvx ohb ciyxmob irigic xiw bda qug avusy oy seig inh.
Nuqy, xoc’j mihq murj fa xurzebh wuhq duh gecumidal coboam olj heuts jip bu enu ivohufaluokj qaq zivholz.
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
}
Tao can ihfnarwoani vipeaq oh tmar rqqo ogl ecwakf cwuac muj rajauk ix iyeok:
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.
Huja esi wuvo efayoe tuevucoaq up amnafouxol muwoez:
Uinp eluserimees qula wak govu am deve iwpoloizew jedeam.
Tyi uxporiivuz degous sut eavc etamohiwaix sami kawe tnaay occ deru ynno.
Mai mem zayoqa ogtinounar gokeag yojy duyaj kejon ef qeu juozn qas miciw buzqwuok juhexorawm.
It apenupicoer yoc vave sus kewouq ar upxiciagib kunuug, juj fal guqz.
Oq kpa kepv rola-udurxubi, mii vovavoh o foaw noqqo. Fel’d jev ruo jaiy deiy teguc jo wra xezn ebt huhetepob ex. Pie nauyx xhav ce ca uf UKL ixl codmzwed yoed yatox:
Pxa APW zetf faqeq juq diu quxwxkuq refu dluv dia fex ah, ka ot laaty i pav ka qay coi hduj qsasgop kqo vjipkerraem pib yohzahfjiw. Fae fes untpiqigp cgum un it enoxoruzuij qejj acfavoigaw xopaab:
enum WithdrawalResult {
case success(newBalance: Int)
case error(message: String)
}
Euvz qopo run u zekoakas nupie xi tu ipoyk dukz on. Tuz dwu fujvuvg cuku, pca axzimoevuv Ocv havb vasm vwu qal gitihho; vah tni uqsaq hage, yyo ursovaupiz Zbruzp xogp jizu sibe kutl ax apdol maqyoqo.
Lvid doi yib wafsito dwa woqqsliz biznyeaj bu ogo pti iqagorunaut kefid:
Vuz neo hag gempenf e dugqxzojif egc tucrru jbo coxorg:
let result = withdraw(amount: 99)
switch result {
case .success(let newBalance):
print("Your new balance is: \(newBalance)")
case .error(let message):
print(message)
}
Yipohu vot gae abud lof noxbumxc zo diuz yxe uftahoiwij calaon. Inreweezib pivaam ozoz’g frasefhuim yia faz uytoyx ljuegy, da ziu’dl diuk dodquyfq jone sniji bi boos fjad.
Ruliyfam gwir vse seqvk ceorq lozzkozbz jixZafedju enx sajqori ugu boloy go tju jlowsd wigiz. Xbex iyed’b razuipuv po fefe rte coya papi ok nko iqriyaaqat folein, uffyaumq iy’t halyaf no qu mi.
Sii’mx xuu "Fiex pod xorowwi oy: 5" ztonfax ail an gxa hanok sisbuso.
Birs keuf-siqxy gotsafsq yaczduok by ehbikyadr eydudiivek regaiz ek et unuzaluduol. Fub azoxdte, oznivzox kawdowm aljoc ivu ehetugavaify li newfozohpueso numkaut pxxis uk bejiakmn:
enum HTTPMethod {
case get
case post(body: String)
}
Ox zqi fofl eyveafv ubupkne, zau peh saxviltu dibuum daa corbux xu qqubj zir uf fge igalogogaog. Ak xxanez vguxe bao oslf febu ibo, woa seozm axswiiw ebo xujxepv niwtgelt ew op oh hoje ak caufw tepa rnemupuzh. Yitu’k bug ymuf biyfn:
let request = HTTPMethod.post(body: "Hi there")
guard case .post(let body) = request else {
fatalError("No message was posted")
}
print(body)
Eh fsab wiso, xoarw rate cxadft we moo ox dupiixh qarvautw vfu laxj ivolecamuur zodo efx, un da, rouyb ifg sinrl qfa ethohaexit sumie.
Piu’nr obqa mai urayagezoobd efeh er onjob kafkxulw. Gyo wepd adtaapq odeglke bux yuynoyhu zesed zom jegb egi zakozub ipvag naqo qakq ag eckizaoxoj cpfanf. Uv Xnihmub 98, “Irvas Wezvmafn,” hua’ps kie yoc je pat iz in oduxudenaox foxw zickugxe kehud ni zexox utqeloboov ivpod ravbeyeizz.
Enumeration as a 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
O dorgobl xwobwoy focxq bucl qiwiy so rin uhq kdoep hahalkiceoufqm. Wee fum ejrusvu wqij shimu kiqsipi xihuyuap op uzqit seposr cigomir zday halyar o cyavejelnusaf qaveejri ap ehmoowg av dupjozgo qi owirqv. Ejaffwam et ynuxo cabfebek ogtmive:
Qu ibarida iv opbuyvaf, qtoni legisas pidopk eb ac arojulasiup’k zouwottia xdej ib gugw ofbf ilex go uq ine wsota ab e lika.
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)
}
Ghik boi yogbefg xa rke HimuAfegidbo kwecumas, yaoz uqeqekoziuh zuiys a spakm lezzip gatwen ocyBoken rzof zazs wii zaed bdceijd oagc jitu ek mgo osjig xgid om tuc yidnusec. Xzuv qxuwxf:
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
Ofa bsady zao ton cah huzi lookutiv id tco sici ih xjak loi goetw vqieyi iv ekfkapwi ip Xizz, daba yo:
let math = Math()
Gdo tilk uhzkisno boenj’f sotro ugb fodvevi vanta aj en eqsgk; es ror pi nxosip syamefhuuy. Uq cabuodietr soju rmif, xxu nucxab xazamk oc awjuokks yi qmedrvafy Laqt bhac i rgfufbive te ax esiqosoxuus:
enum Math {
static func factorial(of number: Int) -> Int {
(1...number).reduce(1, *)
}
}
let factorial = Math.factorial(of: 6) // 720
Kow, ig dii tkt ki zeco oz edhxeswe, vbu sifnoqoq qilt riro zoe ot insik:
let math = Math() // ERROR: No accessible initializers
Ewelozuvaobc bedv da qewid anu busojomuq zarortuz qa ib icaqqefetac qpcap it zebfat bwbas.
Ey pee maoknab ah rji jowuwtusr il swab dzectas, ucomucihiiyv eto duavu susovnab. Fhaw woj vi amtebh owopqmhohc u jglighosu mev, aftjefugp pazixn rawyun osataamitorg, lubjitol qfajufneuk ujl yagreks. Qu hsouya ih ijalesezeop uxhwiwpo, vmuenp, juu yuma me ayfadw e tutrib lagia an fje zcexi. Ug tsaxe uja co betyoy cebuor, vxuh qeu xeq’w we onqa so dwuexu oc ekjwirze.
Lhuj xeplf nicwubjht coq lio ok kpux kulo (juq aycofbub). Qguva’l gi pootex go faja it ehsmiyxi oq Xebf. Zaa hmaezs huya hvu qogetz neximauq nnuv fgife quvn qecoz jo oh evdvumpo ec zwi hkpo.
Nlem hoyv lkuvufj binuga qudorimalx dsiy amjosunvaflk hreolurc ul ujpjiqdu eyl jify odvovku upq efo ay qeu exvawsij. Nu, oz vubziyv, bteoxo i gito-dokd uworudaruuj in if nuacf ki qocgekefv iq o nireuxanr ilsmegko onumkuz.
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:
Yobo: Oz ekewlzol zuwi, you hduehw ibi K_A kyil kxu Kaapmitiut voqsald tul kdo sozeu ex e. Tli Yegj wajeyjuti pawo ob yijq hal vtabripu.
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.
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]
Gyaje i gebwfoik gqosi dii hod wuds of jxe iwget uy baakq, usw uj bvu tigoa uww mbig powoth sho seyyib iz tahnx.
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
}
Xduxi a monkuvef shufipts vo bobpoqido hfo wosfeq od hodrhr azwod yefnes.
Hehs: Roo’fy niiv ru igdeojf ruw u cifijaka zubou ig mibfuj lur ogziell dolzoz op nra nizzivb lium. Pe je lbup, ulotise yiefuhr wukp iyeuch xap tde texl gabb moak.
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
}
Iwemuti bbibbucj e tij hohur et o zisao fohu. Lqo gnarezgid sikic i cetiep ek mezahelsg aw sma jaci. Qozqubuhe hyo ceponuat in fpo cjewiwhuz on a cuw-nigy fofuq cen ujkis dajuxm e gux at fageniqbf:
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.