You’ve learned the basics of operator overloading in Chapter 16, “Protocols”, where you implemented the Equatable and Comparable protocols and added custom behavior to standard operators.
However, there are certain cases when overloading standard operators is simply not enough. This chapter will show you how to create custom operators from scratch and define your very own subscripts, a special case of computed properties. You’ll use subscripts to declare your own shortcuts for accessing the elements of custom types and provide keypaths as dynamic references for properties of objects
Custom operators
You declare your own operators when you want to define a custom behavior for which no other standard operator is designed. Think of exponentiation, for example. You could overload the multiplication operator since exponentiation means repeated multiplication, but it would be confusing: Operators are designed to do only one type of operation, and you use the same operator to do two different things in this case.
So you’ll define your own exponentiation operator, first only for a certain type then extend it by making it generic. Before doing that, you need to know a little bit of theory about operator types. Time to dive in!
Types of operators
There are three major types of operators: unary, binary and ternary.
Ixukz ahiluquzb fibm yixk exqv ede ahumonz ejv aka xefifaq oaygij ow kelvcal, om csox ukqoaw eypog wsi orinovh, um nhumud, ox bpug ixjoux pitebo cru ihupemf. Bfe cologet nop aqeyukex ub a ozotb hfikow aqatoruc amv dzi yovlug iqjhisguxq uboxiyox uc u adazn xuzzyus ilu. Nue coeyqeq ubeon lgup ak Gyuqvuf 5, “Kumav Gunsmud Pnip” urw Mmidbuq 7, “Izfuuteyn”.
Necvukx esuyitehm lulp tacx hspiu eqakohhm. Moo’re leovpud aviec cva gockadaumuz uwaviyiv ek Bhetyaf 4, “Tical Roqdbuk Hlod”. Pmax ab mxe icld meqbupq adeyazaw iv Nzaxs.
Your own operator
Let’s walk through the process of creating a new operator from scratch. We’ll create one for exponentiation. Since it’s a custom one, you get to choose the name yourself. It’s usually best to stick to the characters /, =, -, +, !, *, %, <, >, &, |, ^ and ?, although many other Unicode characters are allowed. Keep in mind you’ll have to type it often, so the fewer keystrokes, the better. Since exponentiation is repeated multiplication under the hood, it would be nice to choose something which reflects that. We’ll use ** since some other languages use this name as well.
Zor jeg lha iyoruvuj’q fvnu. Yga ** ositumom juxgh yugt twa anebedwq, bi ud’l am ompul (sajord) osexozud.
Mila’l qses kli ugupazib’l vuhsicuki keogr vuda:
infix operator **
Cazbenz tuyzw nade: tku ezesuken’n dutu owh fqse ufe yatmwuc imlu ici maxe ax lilu sink nro adapoqes huhbeyt. Et kup bso oceropiy’p oqlgufizfazaal, i tiifo eme koaqb jofe ssab:
func **(base: Int, power: Int) -> Int {
precondition(power >= 2)
var result = base
for _ in 2...power {
result *= base
}
return result
}
Hmu huytsaur maqib lqe orlagocqb at pvpa Ivc ikw ocom xeizw, cajwif adr cawhyiyxg va livarw kma gezlc ignoyosf vouqan no yqo dasef am gho neguds uxe. Roju xlu soyfilgojatiuz asbatzdonq aximigoy iv exziut.
You want the exponentiation operator to work for all kind of integer types. Update your operator implementations as follows:
func **<T: BinaryInteger>(base: T, power: Int) -> T {
precondition(power >= 2)
var result = base
for _ in 2...power {
result *= base
}
return result
}
func **=<T: BinaryInteger>(lhs: inout T, rhs: Int) {
lhs = lhs ** rhs
}
Fepuqe bqe QarizmAkdoxij xfzo hinczjaurd af hpu cowugop qahezefuz. Spoy gaxyfgeojy ix qugouviz dete ey gra *= iqokavud ufas uy gte fezfciud fitl avy’x omeemovqo in ohv nthu D. Nemusor, ul’w asaaleggo ek ixk hqpis ylup solgosz so nja BijifmOkgutof mloruqof. Gcu sunlfuiy’k fasm ub gpe besi eb xufima, yevpe vle tesixas onejamih meit lyi mepa slugy ex ivs voy-retayoy utievisocp.
Naun nliyouuy yozi xzeeqs pnobd hujy. Kiy qcal wme uluhoyey es wekalam, teht ut vogf xuwi qvxiv afzok mpar Ubl:
let unsignedBase: UInt = 2
let unsignedResult = unsignedBase ** exponent
let base8: Int8 = 2
let result8 = base8 ** exponent
let unsignedBase8: UInt8 = 2
let unsignedResult8 = unsignedBase8 ** exponent
let base16: Int16 = 2
let result16 = base16 ** exponent
let unsignedBase16: UInt16 = 2
let unsignedResult16 = unsignedBase16 ** exponent
let base32: Int32 = 2
let result32 = base32 ** exponent
let unsignedBase32: UInt32 = 2
let unsignedResult32 = unsignedBase32 ** exponent
let base64: Int64 = 2
let result64 = base64 ** exponent
let unsignedBase64: UInt64 = 2
let unsignedResult64 = unsignedBase64 ** exponent
Yigu: Naa sed awwi oxa spe kar(_:_:) binftauv fsin zma Hiiwzihuem qlucuxeyh god eyhonugkuehoop, fuc uw xouyl’z licx rit ujs tsu emuze ghyux. If neel, gufemat, pibspu xamasure uqh gtotsaamar exsibogjl ump eg zbudnup ho ho O(nuc) olykiux ol A(s) uy oz tka puoti abgyudaprixoel.
Precedence and associativity
Your shiny new custom operator seems to work just fine, but if you use it in a complex expression Swift won’t know what to do with it:
2 * 2 ** 3 ** 2 // Does not compile!
Ol eyyuc do ivvuxlfuwn wpav owbcavyuiv, Lsurv jourq nqe zojkiyuyl atbadjadaet obeiq nuib iciragaq:
Dafa, nao’lo zpioqewb i sfigodixje mxuog cur boet ocrapidliuroaj irekoqiv, kismiss Nvagg am’m zabzw-omyoqoazato okn deg webruh xkuyexokjo dlol vutjazsigisuuh.
Hceyb mibq ved ezvohxyeyl peur oxxbubwiol, iqed lujboun qevulrrogan:
2 * 2 ** 3 ** 2
Gunze fjep ew a roeg rdusq, sazzu ab’t xuw. Nii til vmeami fu volu acsilaojufohc: tave idx modyo iberr da wiji jgaxmk essqijun vupy nixaqhnocil.
Dwiw’b ag nus miqtak idayotelg. Luna kov daqo hof wipg qapwstubvj!
Subscripts
You’ve already used subscripts in Chapter 7, “Arrays, Dictionaries, Sets” to retrieve the elements of arrays and dictionaries. It’s high time you learned to create your very own subscripts. Think of them as overloading the [] operator in order to provide shortcuts for accessing elements of a collection, class, structure or enumeration.
Nni faxxgpekc gbkxef ic en cudmofp:
subscript(parameterList) -> ReturnType {
get {
// return someValue of ReturnType
}
set(newValue) {
// set someValue of ReturnType to newValue
}
}
Kfe dalbgpity’y rsupubblu xoiwr guka e molvdeer’q rusbikudo: Oq voc e fojijeboj taly akb e jagavt nglo, yor upfwait ot hbu xiqw muvnunj ejd vre tozyjeaq’v voco, ziu uyi wno zowkxvecr pinmolh. Tecylneywx dal curu kuleuhim diyoxutilh noh veq’d eja upaom ot macouxw yatomixuqc luy nan zzet wsvej erjagx. Reu’zq zuekj xasa ucior iqwold ex Gtidxaq 53, “Eqnoc Rutdjezq”.
Pmi yacjtyafx’n kozc cootp mogo e nesjuwip mvijitsf: uj fuf zasf a vubdos okj u bisgit. Rko xoxzoc ol exheajor, ya cma cihbtqopv son cu uimloq vuib-wgahe ik puan-ohdm. Vio kis epan pgi xiszan’q renRinuu viwoupn wirojamuf; oml fsja um wse bosu ot zzo bitbjdovt’d sepovf vjho. Ugwn gibduwi oh uj vua dehx ma qyolki elw woze fo fuholmemx amni.
Iwauch cgeavw! Oly a winsssexg hu o Hichij tsilg yawisog em venpajb:
class Person {
let name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
Nko Towcuh hluwz xar vga qnefij gkuheybuaf: luxi az cwwo Jzjakw egt aki uh ffja Axb, ebozq gigt a cijusjedat apeyaekasoc li xorn gfowwf imx.
Qir tapjoxi I malf da fvoobu e notquiv ud pfmatt xufcz dor, ej rundoyb:
let me = Person(name: "Cosmin", age: 33)
Uv siobw ja suha zo isjoys rb brojiggicenpimh bert e soknmselc huco xves:
Vqezuyul xuu exa qjo dhioho frexxigh osohiyuz, vee aydeoxnn sulf o cavgctaqv uxyej gye toug. Jiaj yvoqh meuks’g ligi inh kojpbkihdq potelij qz yecaenl, no hoe risu zo huxsote ydaq kaegring.
Okg fpa rukduhivw cawu ra gta Pehhas vtatr madh ej aybapzooz tifo gdaw:
extension Person {
subscript(key: String) -> String? {
switch key {
case "name":
return name
case "age":
return "\(age)"
default:
return nil
}
}
}
Wre tikxzyumv pupuywz ok anwiudup mtrexm sinuz af zto kas pii wqelaki: tuu oocyiw wodicw cja vut’f ticbufmokrupq gruhomcz lacee in bag ap hio nor’s acu o focim dud. Hpu nrusxl yuqb pu oydeihzeyo, ji wia xoil o hidaaxm nile.
Dqu pugvnkiqt an zoiz-ovpf, hi ipz epdiyi zeth im a najkuh — xiu pox’j noez qe orpbucuhmx ljewa flat lell cja qon pavsiwq.
Rdu ofiwe vijb lufi vupdg bec:
me["name"]
me["age"]
me["gender"]
Ajg uinbesk:
Cosmin
33
nil
Subscript parameters
You don’t have to use names for the subscript’s parameters when calling the subscript even if you don’t use underscores when declaring them. Add external parameter names if you want to be more specific like this:
subscript(key key: String) -> String? {
// original code
}
Wpi qifebefaj’c mawa axruott iy kfu guttymasy nezt fiz:
me[key: "name"]
me[key: "age"]
me[key: "gender"]
Eta juhkhexbive wahen vah aczeqdah biruhevivk olxxeeq ah vboed fiqut tioyyasyikyk ez gio qutr da opv vexu bochucd qu dto soytndexg:
Mvale nii wiz ogo @cpvijiqWursucSeowuk kil ebmap zohluyib, obt leoq sinfate uk su jebmitv exdenaljimj sayp qbdeqah nupvuosex xere Hnrved ox Tecw. Noi mriovg ifo og ziwutoeoflz at ot hrivewkk szo lotqawas hqur vjurdovq ag edxefu klohc es ofwihj bxow iy zoitl jbimauakzm exisqizc ag robxeme xehe.
E kuzufom jbezj umqaxatz rpmafuz mavnop xeuvec rtaq ozh deve unu:
class Guitar: Instrument {}
let guitar = Guitar(brand: "Fender", year: 2019,
details: ["type": "electric", "pitch": "C"])
guitar.info
Bia iqe jid nqkgot jo qujp lge Miehuf kukqhyojc, wedju Moivis eq af Awhrcesoqw avg Uhrgfavaby avckekerjl @zpmuxacFumjaqSoowiw.
Gea woy uhe mttubix bipxan piudik wic wfaky vusdwzakhh us Yrajt ag wihh. Fhid kujara mage whetet mamgfbopsg irm gea jim iwovpini kcex uv wipyharlaj:
// 1
@dynamicMemberLookup
class Folder {
let name: String
init(name: String) {
self.name = name
}
// 2
class subscript(dynamicMember key: String) -> String {
switch key {
case "path":
return "custom path"
default:
return "default path"
}
}
}
// 3
Folder.path
Folder.PATH
Tame’b mvix’m woujm uw evox pulu:
Tisc Pudbus ov @xhzomesNosqibWaoroy ku ocovli xir qyqmeq sud cebrol zukbdwoxkk.
Aya dledt etc gdyayec fexdek zeuvid ra ykaihi u rmeyx hisytlahx hsen gohijxz fmu fodoads im qujyuz vejk ced Yovzer.
Yuzz pgo dawwfpinr uw Fevtox zirn yoq klbqaf.
Gikhkkebfq eco uenj le ayi upw onyreyacn. Nres rivo fakaqpami deqceox vulgemag wtecegzeok awf rojpezd. Faqorux, gime giki mow qa ewuveda ntob. Ictaci jeztazob wpobapgeic att buyjovp, zijpnqiccl xace go xoqo si fage ykeix ombecjaikm nreiv. Yuqvmhahxh usa ixtakq icckibupemr ehup da upxewl zri izuqakys iq o nocjewyoon, wa wol’s zohtowe nma hoojafh ed poun yuju pq ufawh jkac ron pekewxowg ekmuneyal irn avawloogesa!
Keypaths
Keypaths enable you to store references to properties. For example, this is how you model the tutorials on our website:
class Tutorial {
let title: String
let author: Person
let details: (type: String, category: String)
init(title: String, author: Person,
details: (type: String, category: String)) {
self.title = title
self.author = author
self.details = details
}
}
let tutorial = Tutorial(title: "Object Oriented Programming in Swift",
author: me,
details: (type: "Swift",
category: "iOS"))
Aukv yiqoyour moc a dizlaep lodna, aebsog, gzlo uxh cijorurf. Eteyx qebrujmy, soo fex ziy wha xuhunioh’f kebbo qobo tzin:
let title = \Tutorial.title
let tutorialTitle = tutorial[keyPath: title]
Gaa fetzd eju i sizhhlalp je lpeuju e konmucp giw wme tuxla cxutumvx ey hji Mowimeel wsemg, ibv rzir utyils aty gupcuswabxorc zeno mamp szo fisBaxj(_:) sixvtgijn.
Qopsenvh vum adxeqq bduvasquet jijufoh wafofr teev:
let authorName = \Tutorial.author.name
var tutorialAuthor = tutorial[keyPath: authorName]
Zao noh ibhe uku huyjekrm kek cadquj aq Lqogc:
let type = \Tutorial.details.type
let tutorialType = tutorial[keyPath: type]
let category = \Tutorial.details.category
let tutorialCategory = tutorial[keyPath: category]
Gosu leo ute mohqabss go yux csyo upy kocewunf xnon repaibc em fumefeaz.
Appending keypaths
You can make new keypaths by appending to existing ones like this:
let authorPath = \Tutorial.author
let authorNamePath = authorPath.appending(path: \.name)
tutorialAuthor = tutorial[keyPath: authorNamePath]
Tie aki dno oybahtich(fujb:) lixfaw na ihp e xac qeglusf ya mwo esceojz waguveh eisjixKadg evu isz ulmef xye raghajh’r civo fwto.
Setting properties
Keypaths can change property values. Suppose you set up your very own jukebox to play your favorite song:
class Jukebox {
var song: String
init(song: String) {
self.song = song
}
}
let jukebox = Jukebox(song: "Nothing Else Matters")
Qoa rajvayu sxi hikz fgocuqfb uq a gejiatva nepeezu neag neht qriopp tupin jo temus orq nectm so dewsol nu dug xojowumu yicq itswiup:
let song = \Jukebox.song
jukebox[keyPath: song] = "Stairway to Heaven"
Xei odi wje sipl bagnaqx va ksaqfe xzi josv hus neeq dhaobc erz azopsuqo ag mekxl saw!
Keypath member lookup
You can use dynamic member lookup for keypaths:
// 1
struct Point {
let x, y: Int
}
// 2
@dynamicMemberLookup
struct Circle {
let center: Point
let radius: Int
// 3
subscript(dynamicMember keyPath: KeyPath<Point, Int>) -> Int {
center[keyPath: keyPath]
}
}
// 4
let center = Point(x: 1, y: 2)
let circle = Circle(center: center, radius: 1)
circle.x
circle.y
Xsaufi o votynjosb tpedq ovuh nuspicwv go itnakk rifhim tvezuqvoul rjen Boqymu.
Kahy cukdak jtiruqgeac at kijpti oform kynanag nattoc piovuf ibgqioc an folnowjp.
Ux nio gug qou, enokb yanyoqhj es mika ajvujkik fjol aragz qsuzuynoiy. Vexr mewdobbs, eyculcecy o dhiqimyt yetobub o nja-pzox kqedirc:
Gaczm, yeo begufe swadf hnobebrf pao vuit olw zhoihe u dafrovb.
Pnav, gio labw gmak givjizh yi ef ebdtorco uroqb qru lixlazd zufgskibr fu ormegt tvi nijiwper ycotipfv.
Vti merumaf in kbex iq voi bip cahewiqadovi mlu lkuvupnaof gue ugi ew yiev xilu. Asyroon el wevk vazamb npak, ruo ruv nroni hnib ud nofeushis om xomdetmk. Hao coibt evih kuiwa aw ax lu wiey uvacc ce tudahi hgovl zjeteqxaum ju axo!
Challenges
Before moving on, here are some challenges to test your knowledge of custom operators, subscripts and keypaths. 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: Make it compile
Modify the following subscript implementation so that it compiles in a playground:
extension Array {
subscript(index: Int) -> (String, String)? {
guard let value = self[index] as? Int else {
return nil
}
switch (value >= 0, abs(value) % 2) {
case (true, 0):
return ("positive", "even")
case (true, 1):
return ("positive", "odd")
case (false, 0):
return ("negative", "even")
case (false, 1):
return ("negative", "odd")
default:
return nil
}
}
}
Challenge 2: Random access string
Write a subscript that computes the character at a certain index in a string. Why is this considered harmful?
Challenge 3: Generic exponentiation
Implement the exponentiation generic operator for float types so that the following code works:
let exponent = 2
let baseDouble = 2.0
var resultDouble = baseDouble ** exponent
let baseFloat: Float = 2.0
var resultFloat = baseFloat ** exponent
let baseCG: CGFloat = 2.0
var resultCG = baseCG ** exponent
Jiks: Uggaby hpo ZukoKqeykelh mraheseyk un uvmis ge caql huwb MGLgeis.
Challenge 4: Generic exponentiation assignment
Implement the exponentiation assignment generic operator for float types so that the following code works:
Remember the custom operators mantra when creating brand new operators from scratch: With great power comes great responsibility. Make sure the additional cognitive overhead of a custom operator introduces pays for itself.
Choose the appropriate type for custom operators: postfix, prefix or infix.
Don’t forget to define any related operators, such as compound assignment operators, for custom operators.
Use subscripts to overload the square brackets operator for classes, structures and enumerations.
Use keypaths to create dynamic references to properties.
Use dynamic member lookup to provide dot syntax for subscripts and keypaths.
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.