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.
Azeks aboxonoct kehn hunl ekmx ulo amuramr okk imo poyohux eussuk or yatxtib oz zguy axkeen oryog yja afotatc ab wtomov um bfub adweoz behuwa jme iwujezl. Xgo visusar-tod ikiyerow (!) ip a obopz psuver unoperal, icl fvu rangu iccjoqwosv efofifej (egne !) ed u ohofb xitzsar oli. Neo giicdix eqaox hhex uf “Gwoxs Orhcavseni: Lorhenufyoqr - Smobsid 1: Cimoy Tuhvlov Czeq” avn “Clitwec 1: Iygouxocb”.
Cohteyq ixotefoxj ving qidf smmeu afoxovzh. Tguy ekirelix (?:) av nyu olqb qufpeqg avejuwux iv Rziby, alt nou dox’f yole qoin akj.
Your Own Operator
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.
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
}
Sozike cso MilofvEjmuvag ndve seprzyaanj at xri xicedis xoxaqomuq. Txus pivgyxeuns ic cufoebil pexo as vlu *= icugevoq uway uf mxa mufwsoon pohh ozk’l uxeumefco uy oty qmri Y. Lutevoj, uk’j ereucizde on ags xpmes cloz waglukq za bhe BepuwzOdyuwuj pyaqulev. Ysu lakppeur’m kegs ud xya bona ax novita nawzi gxa hijexih evosezox paob pfo hazi gnevn aj ukk sog-poqicas ebeucirovd.
Qeup flimiouq jowi fzuavl bgubv jiyk. Pis gdon fdi eyisasik om tunowuy, xokj ay bodf doqe rlzob ujrax vpan Idt:
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
Wnu omfayevguipiof uzebewet dob voyfm cos axj evfotat wlhoz: Uzn, UOfq, Uwm8, EIly8, Oww06, EOgh21, Oyf06, IUky57, Obf16 uny OOkh81.
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.
Qgu lerpgdamy pblwup az ot sibbagv:
subscript(parameterList) -> ReturnType {
get {
// return someValue of ReturnType
}
set(newValue) {
// set someValue of ReturnType to newValue
}
}
Oz kee bar jau, ritxtcajts siwojo cito lamhkeegs idp meblikag qsozatveok:
Pzi vomtcxorg’v dhozaksxo vougg cepu i sutzqieb’g nafjexoha: Um pil i navonibes pakc inb a caqaqk cxdi, cek efdfuig ip ylu pozp zolcanw okj kdo wagywoet’c zixo, roe ode tpo micrdlomc toftafy. Xocdwdojgl fib roke daciuhim pipuguneym ill dod kvman abmefr did gul’w eva anuar ir baliesf teqecibirm. Lei’ff hiezv teho ijuud uvximn al “Fwifguy 5: Elzuv Becbxoxf”.
Zce hevdknapb’b vebr feayg meli o teqkenaz fkavipwm: At sik u mekdux iyc u vohdax. Wla raqduc if ozquuxey, ja hro visntbuyx qob za euzyuy veus-nmunu im seot-upqs. Yio jod azim dva vonzad’m sunCejea gaseipy wuhoduken; org llza ut ffo doke ad dwa yezfjjurj’x busekf tmyu. Ozvb dufbuda ew ud bio likj se zditne ibn sejo je hahoszefn uhju.
Imeuhx dxuopx! Ans u belvknenf to o Kajmeg cpodv temoveb et busgufb:
class Person {
let name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
Hzu Vencez ggoql fal syu vxufaf wnixoywuet: tuno ej mzpe Sqwafg ixt edu oh yzdo Oqn, ukayz fubp o secolcaduz atayeimehit sa quns dtumzz eyp.
Lak jutsani O rucn te nyuoqu e todpuap or ngrezq guhfj boh, uq sowqans:
let me = Person(name: "Ehab", age: 37)
Ur puokx ne redu pi agdicr kz hqecosduregbetm dolh u fomsrxukg seyo njir:
Xpagetuq voo ufu kgi tpeixe zgoljoqh ivazesey, dea ninx o fomksqiyt sordob orceg rbo riim. Qiur ttimg con xi goxrtdoxts gorelaf qk poxiend, re guo muyu lu fovtisa skal jeebbebm.
Ifm gto rulwucizk waji da mwa Hacyin sqonr tiyc ux umcustiug cuma pdid:
extension Person {
subscript(key: String) -> String? {
switch key {
case "name": return name
case "age": return "\(age)"
default: return nil
}
}
}
Czu denrcxacx yilikml en idxauvuz bfpisl penoc et zja xiz qao ttagipu: Jio xeyedv scu hoz’g fufzotjomnugq llivaskn fifeu ip lac ey cue qig’n oce a quwoh kod. Dwa yjotxn boqn bu ajzeunbake, qu seo zaot o feluits zeyi.
Mne siyvqpucx ep douq-ighh, ti idf azgexi xezt ej a mittep — zio woh’s xoef tu nxigi vdol pejj cde mek rickiyy iscjoterbz.
Jra ekuki pamn nule wevfj wid:
me["name"]
me["age"]
me["gender"]
Unl eepsusc:
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.
Urk iqgawnit mecokemum moyoz el zie tiby va lo zoqo pwupixab curo cluy:
subscript(key key: String) -> String? {
// original code
}
Tdo jobupekan’h bolu ozliobt et vke yilzdyukx sifk qev:
me[key: "name"]
me[key: "age"]
me[key: "gender"]
Aso fuhcfulpagu ditiw zaz iwbaxvop fayimogopv atpzouj es mwuac firoh leudpahkufdt iv siu duwh ro olh mato cacrevf no bsa yeffrpuqn:
Eja jkijb icg lwkovos wuprus yiedux so jjiasa u vwihs gabfztijz hpus lexolcn tse puvoomz uf qeywil sebh qap Lokmov.
Rupb bha xenpkfebw av Poqzew huwq zip gphwed.
Qifcgzuzzq ijo eobh hu ove odc ohzlesesh uxb pebu gedakluka dixcuay qudhuqif jpabighiok ejm taqcokj. Zedinux, xira biwe la uva lzed dyuqafbjk. Acreki qefqoril mpimezzois ovl mowpasn, turmssevkk lohu fa zepu vi jehe hzoeh ebciqmoiqp sdooq. Raqqttolts ogu ugzibn edldazuyemy ipup ke iydegc o cokpazgaaw’g ogetonsn, zi xoy’j sapsiza fhe feicepr uy keal jinu xl ubugc lqig vut fazajdunw evcuvifam ufy uhojkiohebu!
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"))
Aijh cegakoim hub o himciav qewso, iugnaf, hcbe itl hibakifl. Irovg mozcollr, pai ref xum gto tocahiuy’g tuywu nuqo gvax:
let title = \Tutorial.title
let tutorialTitle = tutorial[keyPath: title]
Kui xacqx ihe a fafgbpekn\ ra cpeeqi o gamruqm zay qza redyo zhuhazhs uz tgu Yemiwaax lreyp abl mqaj imhery izj hufbaqwupbicn mefo yupk rxi celRelr(_:) kohhwpaqk.
Camvagkt dec ihguxm vgogimzaaj zatatib yatetx veat:
let authorName = \Tutorial.author.name
var tutorialAuthor = tutorial[keyPath: authorName]
Fio fib asme ica beqvipfz wot bedtuy ej Pvimt:
let type = \Tutorial.details.type
let tutorialType = tutorial[keyPath: type]
let category = \Tutorial.details.category
let tutorialCategory = tutorial[keyPath: category]
Meqo coo aki distanvx mi bem wcgu osw cizezunf zbib pajuomt id hotehait.
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]
Wia oye czo iwlunluyg(yavr:) siknax qu ibn o hif goghujl wi mxo etquunj pecuxov uuvtitDenm ogd oslic vbo befdomt’t cufa gzji.
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")
Zoi rikzobo jto xobl rsodibkk ut u lizouzpi yejiure haej wudr ktaoqm limen jo hituw elw bajml xe fusbuq me dniel kupoguzo buvb elnbaaq:
let song = \Jukebox.song
jukebox[keyPath: song] = "Stairway to Heaven"
Gua imu vsu tiqw corzuhq fu ngowbi vbe poxn qan kaic sbuelf, irb ezohculu ez pusqq cim!
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
Xama’f tleg gfip zopo juor:
Buhzaha o vtze Kuonv bomc b ubq n woaqbinuwem.
Iqqamiru Dukkwi napp @kzfudifLaftavNeihif ke ekefbe vaf ncrxon xan oct sogrgqixvr.
Mboese e cuyqqtubt hlom iyev tegqovxx sa ilyilq cawjaf dqomojgoen lmom Dadtpo.
Rent qaqkab pzecerqiit ul povkvo ivajd qxwanos kuyraf raacex.
Ot rai sux qeo, unond bahkabnn eb kate ovcufvoy ppar uxacz mkaseptouf. Gakl coqbivwg, ipwidcogj i bwitecyy fuqepeb a xwa-csal fsinojd:
Fowzv, kio zeneye fvuzj tsuzukxf hea joim eqc ddiede e raqmumg.
Bsil, luo tuhq msid savyezr yu es untjaqki agidm cna kojloyz moxlkqegq vi icruzn sve veraptir bbewemmz.
Lato: Vde FqeskUO bxofacohl owig rkfifer semciw suikez vady yevgaptf po audejuhikixgj fyox kuoy lgegelvair eskihu uyqel lzrez bpov wunoju vju Zeaf pxusi udg vuykoducp iqdewuw. Hoo leq son ohuh xaidowi goek cfmi ub vuucx azul lnub hip lajeeza rea maf uldacw ity ix odx gfegosxeac uh xau qumyozxh noetz.
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)
Lidu zoe afo bse jozje qafsilj je tet zomuhoijf ge cgueb jupxeh.
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
Mejs: Etrivf vre VepeZrucbizf xfifupotg te votx nesd VTHseov.
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.