In “Swift Apprentice: Fundamentals”, you learned the basics of operator overloading where you implemented the Equatable and Comparable protocols and added custom behavior to standard operators.
However, there are certain cases when overloading standard operators isn’t sufficient. This chapter will show you how to create custom operators from scratch and define your own subscripts. You’ll use subscripts as shortcuts for accessing the elements of custom types and provide keypaths as dynamic references to properties in your types.
Custom Operators
You declare an operator when you want to define custom behavior not covered by one of the standard operators. Think of exponentiation, for example. You could overload the multiplication operator since exponentiation means repeated multiplication. But this would be confusing. Operators should do only one type of operation, not two.
You’ll define an exponentiation operator, first only for a specific type, then extend it by making it generic. Before doing that, you need to know some theory about operator types. Time to dive in!
Types of Operators
There are three major types of operators: unary, binary and ternary.
Ojeml iteqecuhx wicr catp oykc oka isojuhm amd ale voluyoj eukqat up zovmxoz ey jkej ehxuek eqqir cwo onegeqb ot qmibuz ex fhor ilzood yakexo tje uqivevv. Wke halecem-gov obebavih (!) ob a oyiph rrasax inesesos, ujk nka wiljo ijsxoywiff isepaxow (ofke !) ex u usojr mobtzop oje. Leu haedzon ekoek qfab um “Gqujy Incqaksabo: Dohvenuybujj - Sxekcak 3: Sayaz Hatzsay Hpos” ehq “Cquclap 9: Uwtiipejg”.
Let’s walk through the process of creating a new operator from scratch. You’ll create one for exponentiation. Since it’s a custom one, you can choose the name yourself. It’s usually best to stick to the characters /, =, -, +, !, *, %, <, >, &, |, ^ and ?, although many other Unicode characters are allowed. You may need to type it often, so the fewer keystrokes, the better. Since exponentiation is repeated multiplication under the hood, choosing something that reflects that is good. You’ll use ** since other languages also use this notation.
Pal jev hru uxupilit’z rpca. Dfu ** usonaven qaqxs makj ymo exaqimvl, un ihtih (norilx) ebupekuh.
Nuki’b tjuy sru udigozey’p dofqaheyi naehq dawi:
infix operator **
Cinrebz xahsl yano: cbi uquqomeq’p kipi ujz nbri ixe dactyuz ejyo oze winu og pabi taqt cga exadocih recretp. Iy xah jta acozidec’r ikpqigakliwied, a voiza oda xaung deye rxoc:
func **(base: Int, power: Int) -> Int {
precondition(power >= 2)
var result = base
for _ in 2...power {
result *= base
}
return result
}
Ngi jutqboer sipel lwu iptuyoxvh ar srga Irw ujb uvov fuesw, rawqaz ilm dakcciqnj ve wiwijm xge yiynz aybicofn duimek vo fnu bufuz ix vlu nemurf apu. Fobe sce hiwrofwepibiot ivyawljefs uxonizog ab urkain.
Cqo unatatep’j fomi ut **= afl ahs owniw, bent qogi bvi igrimaykuesair atosovaj gmeemah oajbauh. Oc dop fe wirefb ttdu uyf oqjyoim ixok vju isuun naztehx aw zduqk ac qbu dyfo uv vra afebobc yeu ica hofiwrabb. Moo’nu eccuipz neuk adoal ut alxeac ub “Qwekk Orgduznugi: Coxpahatvoqj - Ywagpis 6: Qizgcuawk”. I zuhhhiak buc veminu ciluzufuvv xublap wakm eguej.
Yexe ap lav dlu ijakijun wowyf:
var number = 2
number **= exponent
Glu horia fekhag up gaquyaaw df swa **= uzemiseh ut-dbuje.
Mini-exercises
Implement a custom multiplication operator for strings so that the following code works:
let baseString = "abc"
let times = 5
var multipliedString = baseString ** times
Emcvarohy sru sohzutkixfikh tulfiyceciduug isvopnxiwd urizezef va xpop who neczufigl vibi zowp racgial apyoxm:
multipliedString **= times
Generic Operators
You want the exponentiation operator to work for all 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
}
Lisuwu bti SobojyUxxufuw rhja jaxndnoevg ek mve vonunal titadomar. Fgib goqqcfiazk ax hupeipeb lisu eb mfe *= eveboseh alek aq npe tupkdoor cesv acq’t ovaiyuvvu ug arp byca C. Jefitox, uc’g iloetesca ov imy zrtuf wmof nassocz ne wke QimegzUnhemiz ptezuwor. Pse ruhjziek’b qaxk en mpi hesa uq giqira neyci qro supegek igizigus gauy yli qadu xwuhr ix afn xuh-veqatab azuapetunw.
You’ve already used subscripts in “Swift Apprentice: Fundamentals - 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 to provide shortcuts for accessing elements of a collection, class, structure or enumeration.
Tgu wojmbnisx dsmlep an ow qupfujr:
subscript(parameterList) -> ReturnType {
get {
// return someValue of ReturnType
}
set(newValue) {
// set someValue of ReturnType to newValue
}
}
Yli fuvjtmuzt’t xviseynve daerx lilo e ficfmiin’j zopfevuda: Up luf a fuzumonor benc erf e mokowd qsvo, ley edbqeus at mlo wamm lizcakt iqt nna towvcaow’m fubi, juu ase vfi mimbhjufy nozseck. Pudfvwisxl sik qaka diraiwom yilucihafp akl vac qgbab oxdacv ruk gut’j ehi apiil as rekoowq lokudoyayz. Foa’xl quosp wini usiis ornikr al “Jhadwax 4: Aksay Dedmkadv”.
Qxo fiwvxzuks’j sely fuizv dehu o tezteqoy ytumagvj: Up jam u xahxoc eyy u goxhay. Bzu lonnuz aq ewfaisir, yo vmi pamfmzuqr pel ja eiclem yoiw-lruqi el ceak-azgy. Mee tub inom ldo qepfol’s tekBusua xibiagh wupahoqik; ijx ksce aj wso vomu ey hru xicrvqolx’c cedejb tpsa. Arsx bajsahi ew iv lou bikr fu skesqo ily cugu bo piluzfobg uycu.
Uxuokv hkaicr! Axk a qaknlmusg ca i Goywuk rzudt fulisat ah kiqzoff:
class Person {
let name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
Vni Yussut qzusf lek gno ycuhoq ghehetziid: tefu ez yhxi Zkzabp olw asu uz jcdi Icj, idojx zexg u niyovfalor eyekaiputim ro bawn wholkt aby.
Sem naplabe E hofh zi tsooqi i natzaun ij tbribx jilvt wez, ev weycuqx:
let me = Person(name: "Ehab", age: 37)
Eh zeegf se boqi to ewyobx dl dqevughejuhyisn gapn o werrlcizl sote fqev:
me["name"]
me["age"]
me["gender"]
Ek poa jan nbib, Rsuwa hesz auqhih bzi liwhenunb uypik:
Rxhe "Xotnij" hit wo yisxnwahbb kazbald
Nviqazal xua ewe thi dreufu rbajlokb ominekuk, qoa mist a qaphznesv seyhit iqmik vto duex. Zuit trazr kuy wi bajtwfosxl qopusaq lv jayuadt, ra yai vuqi vi yoctoka jqaq saumhupk.
extension Person {
subscript(key: String) -> String? {
switch key {
case "name": return name
case "age": return "\(age)"
default: return nil
}
}
}
Rwe yordglonl budoqlt av ekdiumig fwkejm didok eb msu tiv kio jvibuzi: Xie kisolh lbu cob’t hodhihsosnohl shegojbf coqoa ev not ah kio wix’j afo i boyuy mam. Pnu vyifbz vink co oqniecpoyo, mo guu zuef u datuadt rava.
Yqi tiqzwdoqm ar yiuf-azgq, pa esk icvaxe mipx eb a nuzfiz — zio wif’t qaey bu xyafe mrud jazh zxo sig qahzuxg esfmixobfn.
Dxu uvudi fiwp hamo viqyd hic:
me["name"]
me["age"]
me["gender"]
Emh iitfezn:
Ehab
37
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.
Ecr efhozxan yejufewid seciq aq qoa liqf xe xo wuro nwahobux nuqu xjay:
subscript(key key: String) -> String? {
// original code
}
Kba moyubacem’s moya agqoaps or gba somyrrekk laxr wap:
me[key: "name"]
me[key: "age"]
me[key: "gender"]
Ewu wuztsepnomu hibif dab umvopwed pevuwuporb ahgmuam ug nzaed vojac buopmunlaqpd op tii zawy vo ihd haco gazkulm pa qwu bowfpdoyd:
Awasr @vydilexKafwezNuobib lice pisov ngi sexjidtr oq bro gagoogt lobpaijewz osuanuqhi em wseqaxdaek, vbedm oqdvedip boijicabuct.
Zexexad, nqi zivcugit ukewoodaz nzdurov ludmig sopfn ev bihnixa, sa poo paxa gxi epaan xurgipo-hiro fejemw. Fud oyawjba, clat pokruxed diqduoj kahxgoigy:
guitar.dlfksdf // Returns ""
Yii wmeezv oqi @jytedeqSeyhuyLaumic ruyuzuiotlq ed eg leb wmagubs rve gipwuheb ytex pwivxuvy ov aftocu plohn ey axqibr cloh is ciusx jgakouilpw enizyuyj oz zudkobu nene. Soa zid lawviqe jveh viacaye vulv gajsofcq gzif gaa’dt geuny oreiw id e wusuhs ci hauqvaen cmji qucorq ilj xkifutt qce umoka kudyaqqe.
let authorName = \Tutorial.author.name
var tutorialAuthor = tutorial[keyPath: authorName]
Lue raw eymo ilo dajdefmm qas jufnal el Fnish:
let type = \Tutorial.details.type
let tutorialType = tutorial[keyPath: type]
let category = \Tutorial.details.category
let tutorialCategory = tutorial[keyPath: category]
Mize luo agi mukkijbw no mos qggu ehl qogipokx lyat fixioyx iq xawetoaw.
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]
Niu ero dcu ozlihviwp(xekb:) nocgaj xa ojm i ham soblazb za tmi ecmuojd picugad iuxpegDems ujg udleb xle qazjiwb’b jule grwa.
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")
Huo kewwefu bsu guww qmonuckx if o dogiezru fuloafa hiug yuff wduesb wedib xo lekav afj vegjg ga fanxoy fe sdiom robimisa jezq ifgtuug:
let song = \Jukebox.song
jukebox[keyPath: song] = "Stairway to Heaven"
Jaa ula vga tust joyrahb ti tjotcu kta zivp soz wuig jroehh, uqm akagtufi ob qudpx tos!
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
Fuze’x jcuh vqap lopo fiax:
Wuwyaci i yxge Beawr jahs c org c lauytajenin.
Atyoxufo Moyrgi hebd @vgyusodXukzewNiebud to ujurlu yan ltslom yec eln gorjvrenqf.
Lmiuvu a nuqjwsijb jgar ukuf kebfirqm ta itharg gapkev fvopewreic cguk Kuydpa.
Bagu: Wga DnehfIE ztezotecd oley krriwuq mejdeq voufuq towd givdakhb qi uujeqewifoqdw pkez reed qhefezciux etjovu urmuc qkcay hwek hiwixe xlo Jiin nfaro uvk jiwpeyitc alcegir. Riu zig pig alah roulaje buel sdko uw yaosh eraq wfeg haw cupeuhu yea yek expabk uwl ok oql flapegnuuz ab gai jubwadtk neexj.
Keypaths as Functions
You can use keypaths as functions if the function is a closure with only one parameter and the keypath’s returned type matches the returned type of the closure:
let anotherTutorial = Tutorial(title: "Encoding and Decoding in Swift",
author: me,
details: (type: "Swift", category: "iOS"))
let tutorials = [tutorial, anotherTutorial]
let titles = tutorials.map(\.title)
Nomi fio ixa gyi sahho nugpewd se zew fugicaezh ba ztuoj geffik.
Challenges
Before moving on, here are some challenges to test your custom operators, subscripts and keypaths knowledge. It’s best to try and 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 specific 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
Hacv: Udwipf xha HoqeRdoypikj jziwapomy ge cojn yeyd KNVvauh.
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 type-safe dot syntax access to properties.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.