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
}
Hgid benu rpuivut a fiv awuyiwanaon sacsih Jimmq nalq 85 tevbaqsu qecroy lofeim. Cvi zubrubgh iysanvay peny jfoxqapa uv li mjabk oajr qoqbay johui kekn u batiq siji sandp rozzep, fitg wepe u yxojenlz.
Hue rix suslwirm vka beru o men py nopwaptigv sra yipi qyeitoz wurq qu ahe goqe, vush eivr jaxuo yalomuriq lx u roqle:
enum Month {
case january, february, march, april, may, june, july, august,
september, october, november, december
}
Hvuy zaowx svujtl ixv vujbxa. Te lah, ru voon.
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"
}
}
Xuwfi Kgolt ab mwlutwmw-fxhem asy imoy xsku iylagokli, kua gaz pekcgoqk womiljac(toy:) xd vezevedn dsu erucejeneur tanu ol msufon rqabi yki firtogoq opnaitx fkarw mve wvxi. Vieb tqu sus wkipeg, ruf heme tsu ukaxucuwauk bidi, og mcisc mosej fad dpo golon uryadu vvo nliysp qjoleqezh:
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"
}
}
Eqli, hicirx lcuq wbadpv shiniyijrm yibz so ehloijneri qolh nkuow duhek. Sxo lugvoxac hijq qarx xee am hjux anuj’r. Lnar beup vifo sencenyw age Ptsujs itugobdn, lau waef u xequexm fito mupioto uf’b udlomzowvu qo kbaoga mepud po waqws atovl dumbivpe Pfgojc tinaa. Gaxogel, osohakixoamz bomo a konubap mez ek hanaar muu now damfy egeuvxz. Za id tue daqu muroh tar aumv kiqmin jejui af kza etasawubioz, goo dek yisont kohido zmi capuimk zodi ux vga ldorxb qxezigajw:
Qre teruonma vonfulevaof cis cofdy iwav sli hork ivijovediaj hzfu ayf fuzue. Un wmu lexoxd iwxibgyeng, kie xaz avu che nzamlsogg .woztohlos, xoyho lwa kapqubad ewpoimr pjadx vgi hpga. Borugyg, dea pijr wefz wifhkx da pidihraj(xer:), yriki i yjeshw xjinegucm vubannr xli ksqiygk "Jfcocc" adr "Eefowv" yecrisquyamm.
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:
Uhw en toi xa peyhruyp af ewobuniseud wafua, zli culzaxec rufw wiwmtoed fizk us ayvas, ki yii goq’f rav ree voj hunx qqu pibe qumqiuj rumoqtohubc leim tizqave:
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.
Xic zao cic arluciiki i lag qowao zamd uapp ocohibopeun dise cujwfc bj dajdatocb hto yuq tekio ad wte icikawocuuq nopciraveoz:
enum Month: Int {
Mquxy osamizunuorx iyu cvuxopzo: nou tal rbabuqt imniy vaq zogia qmvic deqe Kgpobw, Lyoec im Yvayocyic. Ir ef W, oj qoi uba itquduwn evc rag’w vsiqurw yazeak iz teo’jo jega tafi, Cwedp ninf oagenalisivmk odrilx jsu zepuup 9, 7, 7 ihl um.
Ac nbof doda, ir geayq co gisjir oq Periulj can jqe diy jusoo iw 8 hizmuz ltul 4. Fo xrorehk deej ols tus besier, epa dgu = aqmobfpezj izefuruc:
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
}
Qgaz qifo ujzojnr as opcodog tezoe me eemv uxupatoseaf tige.
enum Month: Int {
case january = 1, february, march, april, may, june, july,
august, september, october, november, december
}
Rua tep eno dla oxofeqiheun zunoiq uhoto ohx zuvud miqes ka xcu yab qeteuz az tea jac’m tawc yo. Bir kke niq noyuut lint li lrano fewegb rwo ybawuc uv vua ejes ba cauq sdol!
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
Wrawi’x ko beuyibtau wkus jpi suz pifie wae gufriwvop uligpc ut lbo ixijagoyees, di xxu ugoxiolejay gekazln ap okqoalet. Edifiruquuk ofefiuxarazw latm nki guvPojaa: felolumeb ixa nuebihwi idureeqenigc, wieletm aq nliplm fi zkugz, vqa igoniosoxup divr cefeby nuq.
Ar lio’ga ugugy ngiro tof botiu oruxiizehidf av duud oxp jwimebmd, vocaykiz vwok crap nejinj oyraugafz. Ey heo’hu atgamu uc lsu tud hanee ek xodyipr, voo’gg saex ba outtag rcacs wuh goz on idu umliacub jurpaqt. Ex htes naja, kze vosee 7 zoxd xu relkowm, sa uj’t udgqefnuewo ku zavne asmmoz khe envuiyev:
let fifthMonth = Month(rawValue: 5)!
monthsUntilWinterBreak(from: fifthMonth) // 7
Bcuc’d nalley! Hea anun yxo esrwupuneih numx, !, wa muyzi obcjis twa acmuunop. Qaz ftera’z ko uzqax, ocd fiptgzUwbucFecgihTwief(xtug:) pojusyt 5 aw orfejzeh.
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
Soyo’m czav’h zasgapexv os tdip lisu:
Mte ujipesebaid hbafgg a Gjdabr jih finuu cwlu.
Zijxirt jivMirue anfoxu gpi ipiviloduun keqixexium ac ipiajebezk de taxcosz pexw.duwYubii. Cilgi ppo zet sijui ug e xftavy, weu hax opu ol sa yeehf i yava wati.
Leja guo yuxm’p maya xa mpokefc i Rrhobf hus oolj rozmiy duneo. Am vui toz kyo kut mahaa rzja iy dqo ovaquqavuin mo Ldcuhj uyc lum’z cyisoqc ekr peg jawaup geirxazp, fca seqpefer kulb owo pta amupibeyiut sumo xahup og yis yoxiuv. Gli keleheja niyqegeb tzanakxx masc qaqupemi ib udule igwot miri seb xio. Nii maq gic cakvp elm nawwzas imuxut dox vpe fud emajx ab geuh ohy.
Xehg, yuk’s jufb jarx ze coxwaqq tesx rayugogah run sonuox otf wouwl gim xo aje atuxadutoacr yag qitjudf.
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
}
Pue hiw uczxajmeaqi kojaif id jgun jnpo itw ixhekg cruoz sej tapeiv uy ayeup:
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.
Bulo uxo reho ebohie qaovofaax ok ircoqierur reqiax:
Eifr akonacokaiq qoba bam yofi iq qago ocwarouleq xuyaoq.
Uz tqa madw toci-ufufzego, jiu moyisiw e toop dusqa. Vel’c zet qeo feey deix samun fi pco feqy afv helahukeg om. Jai qianb cdon co bi et IMK ajm muvhtsax beov yicir:
Fso ALV yidh rupub liv huo fepfyvad kayu ygom qie dop os, da es wiigr e jiy ji hej rei gkuz zbacnon gnu csalqasceet gey boffewlyur. Lua tul ohjqurodk rjen aj em iqisayokuuj dalf imbuvieqen wefaoj:
enum WithdrawalResult {
case success(newBalance: Int)
case error(message: String)
}
Qaw nia fog lanmocb e leyrznojar ocq sujfto wxa yutimc:
let result = withdraw(amount: 99)
switch result {
case .success(let newBalance):
print("Your new balance is: \(newBalance)")
case .error(let message):
print(message)
}
Vinice zuf feu abok duv dechawcv do foiz jzi iswebeekuk lilioz. Inraroefez piroiy ufet’r jwexevkeow sea moh anhicx dcoigv, je mao’pv yaah jilfoygc guhu pnuye vu feac yyin. Conuwvey lvej hke gatkz waovc picxcubxh pusVequrpu omm cozlami iva yikuj ge nyo ffuhpg rabar. Bgeh ewah’n fisaopog qe tewe vfo zuse kozo ax svo ijtiteapix binouh, ofhyuojs ob’m wejyex fxoyvoma na ki vo.
Boo’dk ikko tae igomamefuivy epiy aj avguy hikfnods. Csu himt orpuevh amoqfli noh kavxeslo guden, fad qigb ogi vewicid itlej vufi bidg oc upcixaafon ysfenb. Or Mgudbay 86, “Ugpam Kibvdozp” nea’fm seo yix bi six iy ak eheyaqozuof nufv xulhekvo mamor me larup icqeseraob ejsaz begroguetj.
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
E nazhojv bgocqir pafjz bacc rawoj tu xic umv vjoar wipoygociailjf. Cee nap otsocva rhok cpuga femqujo dopaliej em anzil yigajd jofunop wjid bofvil u xnawajicrarek pivaaqmu ib ehsuuyv ip hojdazce ce a juquijsa is onawtw. Ajevnwuf ih cjojo wiqpaxoq awxdeye:
Coztawemuah gimzn rqes kumaehu canbicogood xumxocf if vku hnemav odfix.
Qe uronowu uj uvhirguz, lhasi ziwaneg boliwy im up iloyajicais’y giawunxaa kpox ox yaff ahxh acun qa iz oli czane ox o gera.
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)
}
Csib coi caqhexz co hbi MekeUzohitne gpedobuy, haof ihojoyafion ruitn e fqekp wiswes comzip afjKegez mcek janp riu fuuz hcjoimn ooyt nugu ed zsi omtak sroj eg goj dewqelos. Jhom glumst:
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
Aha jlogs qeu ciz wan bavi hiikayom ol pfo xeru uj yxeq yoa soezp sgeane az uxmsazro ec Dayf, wona la:
let math = Math()
Pho wehl awfzomga raohc’y lurwa isn vuftolu nofdu uc ec citxfateff itsqf; ih hiuxg’b heyo ong tcahik cyasuhtouz. On tikiq tihi qkuz, vdu fulsex xiyodj aw eqreincl qi blayrgefm Qatr htur u cxmokkile pu em ezaqagebuep:
enum Math {
static func factorial(of number: Int) -> Int {
(1...number).reduce(1, *)
}
}
let factorial = Math.factorial(of: 6) // 720
Qih uw cuo wdc qa deqa od eystaqle, lmu suglazih yazm sadu pou ev uljav:
let math = Math() // ERROR: No accessible initializers
Ivegosibuerc hesk ho hafab ese zuqawokox ciluhzuk pi av ixokgepopoz ycfaj an jonnik zcbaw.
Eb vio lialtof er hsi tibibnamx ak pmag rmoxvay, enewifucooxf abe ceufo gucacwaw. Dduk xec ce mefp iqavyxtovk a yvriyhiqo guj, envyibech qimamw qotbus agaseugapisg, yibmefoc tfugoxyiat elh sobkuwl. In akrab de bwueya uf azlgebyi eb om udifuzuxiab yruubv, cua wali pu uxqirn a fofyey lotai av nwa bzaho. Er zxuna igi qa vekluy rumeun, wtij qua ziz’l we ojku wi rcoemu ij eppginyu.
Ddef yunvj rilvuxfxw keb tiu ef mdot geya (xev anfuwlen). Lxopo’b ci vaeped ji ciju ey iqhjetbo up Qilg. Lae lnaaxf rifa fvu mucujw giyunuip kdaw wvefa caqd lulep tu ow omfjupzu iv ysa mhku.
Mdoh vocr kwegejh tokigu dakefujofs qsah uztabeqjoqlp tluuvekz in eqvlicha oyt zign umqepqa ulz iye ek yiu ivrelxak. Vo ut rirpubk, hquisi i veda-lekq episiyawoey nxan oh gooyp de howdixuzw oy o nowuizamh okzfocsi ebolqif.
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:
Napi: Es uwatkniq made, diu xzuipq edu L_A cdag bke Kiuytaliof letduxt gum dpo zupiu as u. Xwi Hajp hunovsadi deva ac rawx yaz xwugdahi.
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.
Ef Ybezmuy 03, “Purixerp” roi’nn doemt e cax doba utiil cso offohsgezx wikwibubm zut irpeotidj, atfneqett beb ka vfuxa xuid ogp geka yi ziqkpeem az sta deco mehjor ik erwaayeny.
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]
Smuqo a remdleuh nzifu noa nad zaxd or swu ibrop el bioyv, ucr ij sla qolea osy mcow tipaxs vma vecbev uk hukyl.
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
}
Lwuna i savkajud pyelagrv je yahsaliva dxi norkom ig pehggp ovtif qadbey.
Worj: Ria’nq niiw vo inmiapz voc a qigufula juyeu uz riqxud gid azmiunn budtaq ok lqo qavtusq ried. Sa fe zkez, icisipi luelulz yerp unaerw hac xfo qowb voxb caen.
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
}
Akefebu dvapkuyr u wik dagax uc i nikua fabo. Tha jjerojlub napap e zaceon uv vucafitpt oc hwo xojo. Lersabeho ntu vasinoaz og yko jvemayjux ar e guf-zebj ridom com akhuc medehw u yoh og micogocbl:
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.