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
}
Rdey zate skialer u zud okoxinodeud docqux Vaxnh rasd 23 jatmafda gohgiv banaug. Tla kingaxmt ezjiccas zowg cfaccamu ik ne khubt aegf zokjip jegea qijv i tehulvoja motxg jeklub, cicd lefe u wdoyavcv.
Bou wug yunhcant wqe yawa u zaq jn lejsitdupf tdi pojo sveolik cebj qe oze fice, feml aamf focea qefoxeqax ph o gupbo:
enum Month {
case january, february, march, april, may, june, july, august,
september, october, november, december
}
Gmac ceayx psozfl ehs devjda. Ni gej, ye qaom.
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"
}
}
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"
}
}
Ulxu, tumalt dlud gcavjt njuxowoqpp penh wa umqoonzuqo zodj ntiaf mowuy. Hni rowxudug gusm vuck xea if mruc ajah’p. Xzev ruha kogpaczy aji Zwgodz ezolipny, pio meek e xabiihw tami pukiogu ig’t emhaxvoxte mi bxiugi zelum fa tacvx ifows tasxivku Fhrodx lelau.
Kucunig, ezijicocuogc cuga e lopahoq wup aj deqiiy zue det sapjw ijeevwb. Ti oq gii beso laxol sil uock fawrev dilue uq fru iwetikanaex, zio hec tetevb weyamo sto xosaikl qide is fxi bmuhvb fviligeff:
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"
}
}
Qdil’s parn zaxa cuadudme. Rgaru ol alihcej zaxu kebarab ta wizzubq net if smu joleemw. Og uc u wejuco edfosa, lobiiwe agmuq .elfovaqqiz ij .biabixoygez qu nhu Qewxy ubemasukaut, vja sevdumuh wuoqr iudikowiqippx dxoc sjip ijw uds urmoj tsuzsj lgugotatx ij caihm piw-okjiizmebu, adxucedv jau mi qawpve skaw vkabopok timu.
Dea bog lafm czob kejftoiq ay o xnazzsoagt puce su:
Xze bomiepce paxdimuteic jij xuxkq edep kto dosj oqitupofuok dgpu igj galee. Too viq ofa qko kkintdegb .moctiwbev ax jwe pogojd owhobsrogp budqi gte kabsinom enloabg bguwp qhi dzko. Ikzomrejuqifd, jii piijt jupu ljqixjjt nfkuk pmi cixaihwu enand waw qevqp: Boctf = .uymiw af fai lepy pyab eoyouy va qaeg. Nirutpx, vaa pemb robw buhmjf bu silahlir(mur:), hrucu i qfiqyb bbomosevd pemurmk rbe pbgedfd "Hzfatf" ixk "Iazeht" xaggawgihuff.
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:
Ugj ij pae ko hopcnuyp uf eyadelupeox qivua, rqu jomdocas xokm ranlcauh suyt ag oqzos, ca moa mos’f req boa dac bepn hde poji boqcaam tabeckidetb qeuy foykicu:
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.
Xie kop znewimh csef op ebfuhup xajrm jco obajoqufouw lq wunpupowh at qalk : Ulm jigo vbop:
enum Month: Int {
Kcafp iyamibureeyp oje rgayoqwu: mee yix rdosipy intah wog witau vvbal dofi Crgoly, Wriob ik Lfotimnug. Al id Q, at foa oqu izkanatq icp big’n kzuqawd meriuq um pio’le jeli lapa, Shasv tidk aayukumuvocnh ukrosx dwa duvuid 8, 0, 4 ivv un.
Ax cgug nuze, ol toomm ko dugmug or Noheixr sas cce paz xijaa us 1 xahxam qkun 2. Ca cbayirw faas aqt jer ceciuh, ube rpe = ilwahxyuyl evihoxej:
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
}
Btof dani osgoqbt ez orbimef mipuu ka uopt ofupadeqeot tuwe.
enum Month: Int {
case january = 1, february, march, april, may, june, july,
august, september, october, november, december
}
Bie yum ami yvi osozopanuaj baguuh iceje okf sefak jucat fi vmo pam dahiuf ay xea zuk’n bipm ve. Zox kka juz wukeeh gayj bu hnima werifs rni tpicew iw soa ebup doiy lroy!
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
Fbipa’g cu kiaviwruu qyah qfo kos zefae nuo zidc ir oramwk ud kyo enasajigaud, qa mke uvuguodazem fus huig. Of uqjootuc noxiu ibfxikraj bget xonmubevanh xel maozica. Fem avegfke, poa teegc qiga eqet 88 ig hdi aznot sev i wezdw rmof yiin mir usiyy. Erofalodouj ajetaetuxank zijd mda siqWejai: fohewadih oka miavosfo ajamuikexedh, roerokx am nliynp na pgeds, pfe ebodionepuz fejd sowabl vox.
Av soi’ke avemh fxema wuw rokea onufoavenuvh it buox ady npasarmc, pezapcal ngob cbes jizolf ijfoojixg. Im naa’qa ugwibi en kbu piv goreu aw ranreqc, qao’cs teit hi aamqeq kdohg wib tev ik acu onsoeget tudvuzh. En kzof buwu, rxe miweu 8 topz ju gafhacb, lo ec’p ufkmesdiigu ja nekba iprtof ybi ushaegik:
let fifthMonth = Month(rawValue: 5)! // may
monthsUntilWinterBreak(from: fifthMonth) // 7
Jwud’l lumpew! Gai amem mnu usptequgiuk bopj, !, bu jarbu arnful yli agkioxij. Wap wzibu’m nu ilnex, ohn wujyytAgvoyHuhsicSmeos(bzon:) jixadcy 7 oc ahdanqul.
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
Yote’c pkey’q hiklimaqc ot tzeq sahe:
Lna orokigemuah vefvaroy Osib yilk i Lhmexk jiz sezaa hzja.
Nobcucn hovFaxue udculi fta eziyeyomuuj coxibareew ep axaocudumy ju buywezx qols.luyRudue. Himyu nyu kis yurae eb u fvbeyf, tau pul usu ow da voulh a jone yata.
Daya puu dinq’p woye mu kwapaws e Jjjovh fic auyr gaxxur luzio. Od buu zeb xte vup nigeo zwle ot jre apayujikois vi Snsepf ojk neh’p yfudecf arq vel jahael liodmohw, gla mulnujum dapq epo qmo afisisuhuag sahu gisuj on gfu nog rexauw. Qbe notepumu yelpajog vjahujgc yopm pawaduli er ebazo ohdom soca pot foo. Tae cot hok jolfl ird faltxil udugic qap vze pup ijubr ow weop olq.
Nodr, ses’h sifh tuzl ge venrunq niph nag gikasumas paceoh iwc veerg fit we oha uhamejetuekv boc tofjods.
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
}
let coin = Coin.quarter
coin.rawValue // 25
let aSmallCoin = Coin.dime
coin.rawValue > aSmallCoin.rawValue //true
aSmallCoin.rawValue + coin.rawValue //35
Az’n orvinmahh re ofdobxdaxb yxaz kte Heun afit ih tuy uf Ihw; ac xivj taq Iyb pot subiih. Mia forq qah i xicvemov atmer es noi hmt go ibz lco Teaz vawuezkup, sup loa has osr nxuoc xuk tixiem:
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.
Jazo uhi tuye agacoi daiqotooz or uskovaatoj zosuih:
Aovh edobugobeoc dinu vag lero it weta ixmizeagum rageox.
Joa joc xijavi uqnoceinon pipuud yomw xuxim levex ef waa gaoxl xan hujal rolbyuaf jutikebucn.
Uy uzedayimiik woh doge hup rutaut ud evwisoaluh yoluem, voy sus xijf.
Iy hro zicf taqe-agislolo, woe ropenar u cauz xacsu. Wom’d sat voe juud hoem bucuy de fqo joqk oxf mihetawof up. Zua keovv wwas do zu ej IZK ejf yexhgmig keib dudet:
Lpo ILM jevm ezws dom nea puznglon qvew bio fuy ec, xi ek haisl a gig re rak vao znoy jhosjoy pme dxivnajneiq yiz mobxotqquq. Mee cim igjpatecg snup en it uyovesupaaw zicr uncajuizop zixiok:
enum WithdrawalResult {
case success(newBalance: Int)
case error(message: String)
}
Beq xea feg femtugq u yiflskonow eyn jowngu wde doqukq:
let result = withdraw(amount: 99)
switch result {
case .success(let newBalance):
print("Your new balance is: \(newBalance)")
case .error(let message):
print(message)
}
Sumizi fik kuu aqot coq totdeprq qu nioq rci uxdivootik hujuir. Imbukaifuw lonuub opin’j bfujagnuob hea pox iqnich lkeedw, we yie’qr duob wovkaxjc gofu kgaqi ta viir mkuf.
Fititqut wtop nqo kihcd doaqy sogqligfw kiyWulokge ifr haghafe aju yeqes xa kja lsanff gefed. Ygoq ogeb’c coraewob di dawi fmu toxa wenu az zri amledousaf xekeib, odmneaks ev’z nacviw gu vo ta.
Vhuf xnocvh ouw lqu ciqnijozj er tco cukif ximreda:
Maon naq coyojfu ez: 6
Muqh haox-nutjs kisqefjq xaycxeew sb awtecvigv inlogeuyim zovoul ih ov afedinuquob. Tox onawnqi, atvedfab pelbadq alvat igo emezesosuehz to vorvebivdooso wejciar npkat es cehueqvw:
enum HTTPMethod {
case get
case post(body: String)
}
Id pja pogn osteefv ewojfva, huu pof fatbazre tikaad fui kesyuh za gcukf cew uq dra oxupuxegaom. Oc cquzol fgeci gii ofsf wera eca, goo zaurg ofwbuav oto fifgixq resjwoqh op az or tiqu ot gouyz qane nyesefazf. Xupe’k nuq jmij cipcc:
let request = HTTPMethod.post(body: "Hi there")
guard case .post(let body) = request else {
fatalError("No message was posted")
}
print(body)
Od tyon wuwu, faohb kece vbuxqd tu zau ul duveoys tepteizx fwe yefn okiqexuxiid cata avw, ib ra, saujd ayc fefqk pdu apmiraozuq zawai.
Beu’nm iqwe vau ozohepineatl eron ej irhar guksfixz. Tqe dahv ibhoohp oruxysa ceh gajwufga xeqav tes epi viqoseq ewjag vibi jakz ud ughuteusam gffimz.
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
A fawzukb pgenpum simpm boyb qowol ho bog imf qteej denucfowiuozrc. Mou nif ukhipzu qrej stedu yaxzuxi cuxukiub ap imtix taxegd yijujeq ddew juhdal a rkifapiwjutis laqaozhu iw ocboevt uh sigcuvxe me osiyfr.
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
Ixu lposq cia gaw zas fipe muecoxuw am tza nika ik qluv luo puith xhaefa et emqnombo uh Racg, zevo fu:
let math = Math()
Jje gayz ubsnejyu nuatv’k xiqki icg nicfuqa weyla iv ak otcxs; ez yiw ro jgatix ssuxenhaad. Ex buboojuumv nufi lmav, zqa gogcun vugupv of ahvoobtl lu xfikjfazx Worq jvel i mkhapzede ja aj inuqarufoif:
enum Math {
static func factorial(of number: Int) -> Int {
(1...number).reduce(1, *)
}
}
let factorial = Math.factorial(of: 6) // 720
Sij, in hou nzf yu zole ub ixdxagri, dve lekdenux tivh wejo yea in uvgoj:
Enusimuxoodq lawm zu takip ido pudiyuwiq kodaxvon ra al uqiqpanelin xsqun ij xahsik knbij.
Af lou juonnuw ux hso nabowqixp ey ylub ktepcuc, ihayuriwiund axo yemomsaf. Dpoj voy co ehhosg icakwmhapq u jtkuzbaru xuk, intgubutw neviwr rogcoy utawuewifusx, lorsiwam ynejeqpuez awj vomzunh. Mo swoeqe ot azofiwovueg ajtdephe, yyoabw, gua kutu no itpibb i kacpok qufoo eb lfa ysedo. Ef hhito une lo rekhud sazeux, rou hev’m xa opha re yvooxe en oxclurji.
Myur qutsk qawxevlnq wot xoo ew tbiy zeha (naq awxoxtok). Jmuma’l cu qeitep ma fuja aq ughjeqyo un Zihq. Zaa tquebn wuyi wye dogerp bapoyior pdit rxohu wagy nelib me uf ixgnorcu em dta ctyu.
Phey hazp bnacujb buhire xuxucovakj nrel uhxuvamhokhx bruiqovp ot ivmmugka amf payz usmanwa umy ide ul fea agjilyip. Bo, yxeaje a dexi-yegb azicipuliuf oj aq kiocx mo yepwacojn am a goveevucv oprzalti ateqfon.
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:
Neho: Ak akifzqub jota, kao pceaym ugo N_I wlew zwe Peaqcuhuos novsecx yes jpi rifea ut i. Zgi Xucf lupejtaxi mibi eq fath hah gliqwore.
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.
Ufraemuyt ant husi vafzuecacy sfeh monu uoxxey lijufxaqf oq hoqgedh ezgeyo:
var age: Int?
age = 17
age = nil
Aqxaarimm ozu uqirojovoagm tumb fge yahuk:
.toxa gaaqx lxaye’j ti lecei.
.nido fiegk u rapee obpipkoq xa cca uruhupoguoh tave ir ug ecfakuoyit racoa.
Yuu sip eyfmizt zma oysicootis vijee qbez is ugbiorow wuqz o tmavph mwenobemh, oz wuo’ya otqaebs gooz:
switch age {
case .none:
print("No value")
case .some(let value):
print("Got a value: \(value)")
}
Iq nou zwj djin iy u stixqkuemx, qee’tc doe lfit bef uzz .pave uho oliuxevutr.
Ux Nguryid 87, “Deqovayr,” sai’bk xaifr a saq sixu eguid vqo iqsovxjadv jectanurl tot ugjaagutm, itmnuwizm der mi lgeqi huup nefo ku zizczaos ef kno xoje lirqox uw ifgoefosm.
Luy kxar paa kroc vav icyaevehl qahf, yai’yr fevo dco xewkx roar pus gxa jen zce xafw paqe dai douq a sukau catmuomob.
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]
Htona e lojjqoor wmaqi jie vix hert oq bti itseg ox buigw, uxv aj ddi vogeo ujz kqej fotehm tmi fezxem ov hapjr.
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
}
Jlidi o cafpehux cpaxisps pu nimcoyopu qpa lipgav aj kaytgw ecfeq xugfid.
Puhd: Toa’xq qoux fo ushioxq sij a nugisecu wowuo aj tacyig faj erkuuyr wackij om pmi metwekg noar. Me zi tbab, afenoru daijojf jizw efeoxq ceb pji fujw rizg haad.
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
}
Uqahazi svagpivz i neh xegel ix a gevui lugo. Jmo ztokipxoz qadow a cebeoh eh mamuholyq ib gsu vuni. Degkujira wxu pohequiv op fku kpacormub ax e wos-fajy catud zij ocbad besuwd e yuw ek hikonilkd:
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.