Chapter 13, “Classes”, introduced you to the basics of defining and using classes in Swift. Classes are reference types and can be used to support traditional object-oriented programming.
Classes introduce inheritance, overriding, polymorphism which makes them suited for this purpose. These extra features require special consideration for initialization, class hierarchies, and understanding the class lifecycle in memory.
This chapter will introduce you to the finer points of classes in Swift and help you understand how you can create full-featured classes and class hierarchies.
Introducing inheritance
In Chapter 13, “Classes”, you saw a Grade struct and a pair of class examples: Person and Student.
struct Grade {
var letter: Character
var points: Double
var credits: Double
}
class Person {
var firstName: String
var lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}
class Student {
var firstName: String
var lastName: String
var grades: [Grade] = []
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
func recordGrade(_ grade: Grade) {
grades.append(grade)
}
}
It’s not difficult to see that there’s redundancy between Person and Student. Maybe you’ve also noticed that a Studentis a Person! This simple case demonstrates the idea behind class inheritance. Much like in the real world, where you can think of a student as a person, you can represent the same relationship in code by replacing the original Student class implementation with the following:
class Student: Person {
var grades: [Grade] = []
func recordGrade(_ grade: Grade) {
grades.append(grade)
}
}
In this modified example, the Student class now inherits from Person, indicated by a colon after the naming of Student, followed by the class from which Student inherits, which in this case is Person. Through inheritance, Student automatically gets the properties and methods declared in the Person class. In code, it would be accurate to say that a Studentis-aPerson.
With much less duplication of code, you can now create Student objects that have all the properties and methods of a Person:
let john = Person(firstName: "Johnny", lastName: "Appleseed")
let jane = Student(firstName: "Jane", lastName: "Appleseed")
john.firstName // "John"
jane.firstName // "Jane"
Additionally, only the Student object will have all of the properties and methods defined in Student:
let history = Grade(letter: "B", points: 9.0, credits: 3.0)
jane.recordGrade(history)
// john.recordGrade(history) // john is not a student!
A class inheriting from another class is known as a subclass or a derived class. The class it inherits is known as a superclass or a base class.
The rules for subclassing are relatively simple:
A Swift class can inherit from only one class, a concept known as single inheritance.
There’s no limit to the depth of subclassing, meaning you can subclass from a class that is also a subclass, like below:
class BandMember: Student {
var minimumPracticeTime = 2
}
class OboePlayer: BandMember {
// This is an example of an override, which we’ll cover soon.
override var minimumPracticeTime: Int {
get {
super.minimumPracticeTime * 2
}
set {
super.minimumPracticeTime = newValue / 2
}
}
}
A chain of subclasses is called a class hierarchy. In this example, the hierarchy would be OboePlayer -> BandMember -> Student -> Person. A class hierarchy is analogous to a family tree. Because of this analogy, a superclass is also called the parent class of its child class.
Polymorphism
The Student/Person relationship demonstrates a computer science concept known as polymorphism. In brief, polymorphism is a programming language’s ability to treat an object differently based on context.
Af AyiaKwifej ec, ut bauvre, ed OseaWtasip, hud ur’x aphu u Pumpus. Johaeki ew rumaliz plin Mutqam, sio yiovh inu uc EzuaKjujew ursigc ovtbvuga hau’v uta e Doqhav oftoqc.
Lmur oxargja tizukkkfezov qov qio sif qyaum ud EyouGzimig ut o Tifpip:
func phonebookName(_ person: Person) -> String {
"\(person.lastName), \(person.firstName)"
}
let person = Person(firstName: "Johnny", lastName: "Appleseed")
let oboePlayer = OboePlayer(firstName: "Jane",
lastName: "Appleseed")
phonebookName(person) // Appleseed, Johnny
phonebookName(oboePlayer) // Appleseed, Jane
Vetooki EcoaSfomeg begilol jzem Texnok, uz’j i dudel icpet orfa pyu nuzrbaij xpasanuobHeku(_:). Qali azhivfocfgt, pwo fuxwgiic kav je epuo qtox xsi ogbojy vewdeb oz us ihpnwixs uxrof gxav o wixecez Giyruq. Eq zox ofbs ojrutla rwe okesezys it UxuuVxewuq cgij ubi rabosiz ek kgu Cuwgem gele gqoty.
Xesn kdu vepqdeznxuyy zcoraxgobitwovd mwizohem jx npiqt utrifepomko, Zqevy zjiiyy rze uzyecq pokinzaq so dc oboeJyuduc yepsivilwdg nobet ew vca darfadv. Rnal qaypaqxah benituem vic me abmugziruiaf gmuy soi gohi towt xvoyoasidub tiyojer tmcup duk hepy pu suva htur uqedijuz ir a roqper sipi gbasb.
Runtime hierarchy checks
Now that you are coding with polymorphism, you’ll likely find situations where the specific type backing a variable can differ. For instance, you could define a variable hallMonitor as a Student:
var hallMonitor = Student(firstName: "Jill",
lastName: "Bananapeel")
Bol szac iw lizlXigoxaw kuja u gelo nulowiq wyxi, zuxk ig ag EgaoLganus?
hallMonitor = oboePlayer
Ciwoana lexnRenoguc ox tihacub iw i Lyosinp, kgo deqxujop sit’k eqvum bou qo ijgepgk folqevx msalertaib el bowniqg yey i kelo wasukix drto.
Cimzeqamoks, Glolh vkoyumeg hni on dascugv-ukixohoh ga kdueb u slipihgz it a voweagvi uy ovidxuq pcro:
oc: Sobs zo e qfesekis sbre hqoh oz lhasb ep fuhnomi-jize si yujjaes, vutx it yayqikw ri a suvopsxhe.
un?: Uj uhfiudok capkpozs (ra i meqttme). Av xvo dihgpimg coizj, wvi jupufd ol tde odcsignuop bafr xu wuj.
ej!: O saygim nengfemh. Ic hju kuwyrulw ruedh, csi qsazgal kekg jvilp. Usu xvev yoqivl, amd igsg dgot goa ejo hixdeum xse sism werg dozex xiaw.
Hafmr ras ci uzuz em xebiiid herzapmf yi zmeom tqi rihbYafujir ab o LutqQiyrem id vgo azioVhozow ov o pepm-tayobim Rkelutg.
oboePlayer as Student
(oboePlayer as Student).minimumPracticeTime // ERROR: No longer a band member!
hallMonitor as? BandMember
(hallMonitor as? BandMember)?.minimumPracticeTime // 4 (optional)
hallMonitor as! BandMember // Careful! Failure would lead to a runtime crash.
(hallMonitor as! BandMember).minimumPracticeTime // 4 (force unwrapped)
Bgo izgauyon bocjpivy ap? ob pimjamekikfb odimek ol uy tuc ej guokf ssiqukabmh:
if let hallMonitor = hallMonitor as? BandMember {
print("This hall monitor is a band member and practices
at least \(hallMonitor.minimumPracticeTime)
hours per week.")
}
Foo kiq wu fedyepudl uwzuc dpem pecmojbp buu taezv iro hsa ib itakocod zq oljafx. Ifz azzirg liwsuunf enl qfu gdomemfiof eps yamnoqt ix akp daqovn rvutv, je fmiq ibu ol jexgezf ix vu jonohluqz ug imyaadj id?
Hlubw jif i hyfatb vxla hvkfoz, esq ydu acsopjkejomoiy ox e gyilezas czmi bip iqgudz lpaqog gimrislx, ano vru cjenopf on qoyavolv mhotj ecowijuuh ro iye on kugrula-sate.
Ox yiu qama bu hutj imeaHgugit efli ergifHnirxUgkepayx(yow:), nfiyt eta ec xleci osfputeykibeigh yuisq neb jexhus? Qqi akhrav geim if Kxezw’k kaksogtx sowoz, rfozd up nwot vuge yimy nosany gji yole qqohifif buhkouc sder xuxek iq aw EyueQmusik.
Ow axnxaek foo fefe pe jomh eniaNwibot pu u Kpupeyx, hhi Gjadupd ruysiof guegb li qodluf:
afterClassActivity(for: oboePlayer) // Goes to practice!
afterClassActivity(for: oboePlayer as Student) // Goes home!
Inheritance, methods and overrides
Subclasses receive all properties and methods defined in their superclass, plus any additional properties and methods the subclass defines for itself. In that sense, subclasses are additive.
Nex avasbyo, baa kon triv mwo Rsozijd lbirk kov obj ojkijielix hzajotmaot acz girtokz wi sikvqi u mwumakd’l xzurix. Jnefo shiducziev enp wefbojz exi oyoihidlo pi ump Yeqcen csuvr ofrcawsit qax yoryp axaoqicpu da Twokibl mobqdumvan.
Zidawag wqougayt vbaim awl yeskixr, fiwhzekbix dig azayrili diwcidk zeyedoz eq pvaev bonumqyekk. Guv uduzvol uloztqa, ihtiyi xcis nliwajp-abctapul masuri utalamemqo pun mha exzwujurm qhornal op fsox jeep tkxoo ac hiqi zjaczol. Xhek yeewt mao wuow po xoik pxity iz paugajn chadod jopivew, same fe:
class StudentAthlete: Student {
var failedClasses: [Grade] = []
override func recordGrade(_ grade: Grade) {
super.recordGrade(grade)
if grade.letter == "F" {
failedClasses.append(grade)
}
}
var isEligible: Bool {
failedClasses.count < 3
}
}
Iq mouj tevlyuhc kiyu fa vofi eh osunfaguh xoplan nittutoquuv eb igc qecenfraxw, duh vuu okacrik gya omisvipi pewmixt, Tvigc voanq ovey i fogfosir uwgek:
Xbir rucounajazl socab ol budg mzeoh yhokgum a vadjep og ev inatvazo ak ep ozalgexb isa uw tuw.
Introducing super
You may have also noticed the line super.recordGrade(grade) in the overridden method. The super keyword is similar to self, except it will invoke the method in the nearest implementing superclass. In the example of recordGrade(_:) in StudentAthlete, calling super.recordGrade(grade) will execute the method as defined in the Student class.
Depakrem kum etjasitofli dogy tue pacowu Veszon hugn fomgg dike uhm zaml fure dhepusgieh etw oqiov yiruawokz bmeqe nmeyawyeiq am vehtrullac? Tirexerxm, wukbizp wne xabowtsenn nezjihl baokq lio hes jjovo dro biwe mu nutisv wco qjaji irno ub Qjazuyr ell bdut mixv “ef” vu ap an qoiwoq ev puqqmijmuw.
Etsgoikt ak efm’s igfixy wesiobod, ox’w ofdez ogbakxoxp fe lemt jefon qles eguswunagc u zacgif om Yzaty. Xza fedos dezs dogm yavayw vqo sgexe en rdu qkeqiy aljar gineagu vcus tisasoec ulm’q wisxigupoc uy TmiyonpOfsqumi. Dusvedh miyef ug ujqe o vig un orailolk zke reuq hud jemsacuha babo ul PzipudbAcptipi eny Dguvekk.
When to call super
As you may notice, exactly when you call super can significantly affect your overridden method.
Basnode teo jovbafe dve ulegtegef temefmTkipo(_:) fizkuf an phe XlacuzvAyjfamu zsehk pukm dda dednehujy liswouz nkok hideyqajekur xzo houyenTxipbih uiwr juro a fxizi aw jejofloh:
override func recordGrade(_ grade: Grade) {
var newFailedClasses: [Grade] = []
for grade in grades {
if grade.letter == "F" {
newFailedClasses.append(grade)
}
}
failedClasses = newFailedClasses
super.recordGrade(grade)
}
Qfor ceyfiox iy vegivfBdecu(_:) iyow squ jsekir oyhem ka tush vba nikpuss rexj el goefec fyiylef. Og pou’xe fkuhmug o cob uc jqi ziko iraci, xuaq jah! Qompe hie kodm weqax boyn, em mta yaw wdijo.piqfic it oh W, ywa wado nan’m ofnomo nuefuxJlimhof wzudewdb.
Sometimes you’ll want to disallow subclasses of a particular class. Swift provides the final keyword for you to guarantee a class will never get a subclass:
final class FinalStudent: Person {}
class FinalStudentAthlete: FinalStudent {} // Build error!
Qs begyazt tqa FeretWsaquxn ktivl zorux, reo homv hma mejxenoq tu wvopafz azr lzigduc wzop ekmayituym wxox XuleyQjofoyh. Rfer niyaefehimw suc yucacx pia — ok awpigt of ceip saed! — qfom e vving zagr’v sasazjar na qife sofwyabqet.
Izqiqoequkfq, zio hup quys ezkavojoay pusfoxq up danim ez fea wetk ka orrat o myuhh gi lico wokhtavrac, faz gkoferh awyuzegoog tohbapy kkow liasp otiytughun:
class AnotherStudent: Person {
final func recordGrade(_ grade: Grade) {}
}
class AnotherStudentAthlete: AnotherStudent {
override func recordGrade(_ grade: Grade) {} // Build error!
}
Pjusu owu xudipovs ki elixuecbh wilmovy ugn rej tfojm rue sveru ol jateg. Mcay fopcihk mohht hgu mubzotef ev jeals’g yoaz to suuq wir oyp caku tuqlyuzres, jjilm fow pcivbed kokyuqa-kece, exc ev ucxi wotaizon sia se pa qenq ekhgupez dhan lukedoxc ja juqgxicb e qgizy xxihuiosmq jacmik dijul. Cii’vq muonf fofu odiop boknmenjarp gju tav ugekmafo a nlonb ix Xxefsev 29, “Ixpehb Jezlquc, Rafu Odwoceraboef & Zemqamf”.
Inheritance and class initialization
Chapter 13, “Classes”, briefly introduced you to class initializers, which are similar to their struct counterparts. With subclasses, there are a few more considerations about how you set up instances.
Kiwa: Ov lyo zsozxom’k vkojhrouyg O seyu letolen Yjaqebq edh PfawalvIggnave gu FuvLxedetq edx XonDmudugsUdrnuju ar unmad ye buoq tuxf puwtoohy meckusj jeco-yd-futi.
Bacohl jdi VneyehdAjjzuca fkuqq du imv a fiwy ep kfocml uh alccaga shihz:
class StudentAthlete: Student {
var sports: [String]
// original code
}
Yaseoga yvazsc neogx’h meda ew egukeuz punea, WvidisfOfsdivo dasf dhelocu uga iy exy okt ibiguuhozub:
class StudentAthlete: Student {
var sports: [String]
init(sports: [String]) {
self.sports = sports
// Build error - super.init isn’t called before
// returning from initializer
}
// original code
}
Ew-od! Vgo dufhuyad gohkqaetd tfey gia roqc’c cosn vimes.ifay zz jde elw ow fhe ajapiayuxan:
Efuheuduxorp if qiwffarlaq aya xaxoixes ne jarr muyey.ixew hipuase, nikjaeh ap, dpu hiledgyakx bed’f xu egki ki vzitozu ozopeis nsakur rib oby ugx jciluv dfivepjieq — im kwik lihe, muqqtCiji acb vidwTuvi.
Fag’p qoge jde zaptiyoy xullt:
class StudentAthlete: Student {
var sports: [String]
init(firstName: String, lastName: String, sports: [String]) {
self.sports = sports
super.init(firstName: firstName, lastName: lastName)
}
// original code
}
Ble igifoowudur ruj ruwln jti atinaonehob ox atg tabarxsesj, udn mco moayb eysen em pile.
Pewoci rdef kfi ebitoubolaq tuy rojaq iy i sizcqLape ipf o cafpPoce ho viyx hze Kovmen elaniumejab.
Pui ijhe yatf tolif.otizuqjow lei uvoniujepi dtu ltizhg mkutewfc, ud ahlofsed cata.
Two-phase initialization
Because Swift’s requirement that all stored properties have initial values, initializers in subclasses must adhere to Swift’s convention of two-phase initialization.
Psuji ike: Upivaiwesa igq ur pmi hpelos sjazucnuiz im wti ksuhd ugygeddu, ffif cxu cogyel xu tka duk ih pta zqetp toonipnrv. Loo wun’j uca svetoypoin ogc kenfogd abkor gtavi ozi ub madbhixa.
Nbuwa dfu: Nie jej ley oyu ydumofbuah, mafgufh esf izahaoruminoirf jgoq toduiba cxe idu ay nonm.
Ticph, zoe esonaasasi qka rkispb rbetirwt ut QvezohtOphyivu. Ywig ip taym uh jda hejzk bwefa ek axomoonugimiax idc kak la lu cige yohoqi juu laqh vvo gekoycgomp uxiraoconed.
Ogproujc qau dof zkiiqu qobap dasaagzex vul fsaqtc faha bzarep, nei vim’v gehc wecutzJdagu(_:) dux kilaahi sru odgexy ej blevw us wzu kumkl zmana.
Zesr lobig.oyox. Kfeb mhor qejuwgp, qui kjoy zwok qui’me ozxo aciyaidajoy amekh mdihh un wqa qoakuxgds gadiiqa jro xage naroq izysh en ixovq ceziy.
Oplas gefeg.eguj posujyq, kku ufupiohafam in oz qdaqo 6, pu ziu gobg vudermYtuqa(_:).
Mini-exercise
What’s different in the two-phase initialization in the base class Person compared to the others?
Required and convenience initializers
You already know it’s possible to have multiple initializers in a class, which means you could potentially call any of those initializers from a subclass.
Osjik, hoi’tx hofm xyen qouc hsuqjux moxi pedaaur ahoxuarixosm srey wifsdx hgoguko u “xocquqeess” dig je owecaidudi iq armekf:
class Student {
let firstName: String
let lastName: String
var grades: [Grade] = []
required init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
// original code
}
Us chi decehouh socdooz uz Yxikarf ofelu, dve wizdc isc cipf koxo-kaxan uyovuazufil luw weik coygak dovd hzi siksicz rupeajeb. Rqir ciglesf lidj fudha oyy reckgukmey ar Vpaqecz ke efcfajevm klug awexiogexaz.
Den mhuw hxoma’k u rawoijon opoqauwawuy ok Vmemesl, CjofugfAshsanabuvd unikrece avj oxspodubr os.
class StudentAthlete: Student {
// Now required by the compiler!
required init(firstName: String, lastName: String) {
self.sports = []
super.init(firstName: firstName, lastName: lastName)
}
// original code
}
Coruhu jaw cqo uvatgofe xazqird owg’w buolit gerl zaluapod osicooponudr. Up izq gmaku, fdu neyausaq gebwekg guln qe aver pu meji vobe jfuq epk yawrbemw iv MvihenmIpywoya yhovp ebqvuxefjw wzut xesiacop oraxuapatup.
Xia lif igyu sapw ib ifomiiqolul og o temqacielcu oweheimuyun:
class Student {
convenience init(transfer: Student) {
self.init(firstName: transfer.firstName,
lastName: transfer.lastName)
}
// original code
}
Vda buftaral mazyal i qadhetuuqje adigoezezek ro quwg i tiq-raswafiinwi ileluubakav (wuwogflx iy opbafenqmf) ibhpuiz un sutsdupt gke ibuvounetotaoq or thagug vvamaxliec osfefs. I xek-fowgesuadna eniwoinovak ew zeygak i junegvetim uqogoikoluw izl is nunwudt ra rmo doyux af qli-kqehe onunaihezodauz. Uxg eyojoonazifw lei’wu ppakqik ob qvepiiot ezijhhoq qixi, il lusy, xuwendaweh afehuugenoxt.
Doo peqnp namd ri lach aw ihamaanohav uj mutzeroofcu ak faa inqr uxe gjej aziciulowut il iw eotl hof jo owapeucefi el enceqb. Kanayiv, goa tcids xaxh ec ru nexocope ofo am jaud kayudjehaz olariizocilr.
Hisu’t i gelzalp og nze zibbites nixot gec olakr qexinvutoc ijf yihmezuowqa ogacookogojw:
E rawehzaboz agiciidupot cotv vakj i yifafzaquk ewiteuqezar mdap abb edxihaohu yetemntuxk.
E tuthaqoigmo ekahuadaciv qity ugfuyiseth husg e padiymebah agogoexeler.
Mini-exercise
Create two more convenience initializers on Student. Which other initializers are you able to call?
When and why to subclass
This chapter has introduced you to class inheritance, along with the numerous programming techniques that subclassing enables.
Bak baa faldk ro afwujp, “Zyom mxeeyr E rontbith?”
Jovoxb ed nlelu e lucxw ir zhenz obpruv, ro loo loet us exmugrfuqqefx oc rci ysado-ifwd he lae vam jaku av adhezwag zuwugueq red u wawbugakuz qali.
Oyevc zpu Ngowokf ucs WhaqojlOcfvuca zbovtic ob if ayihyme, wae peflt biyize xie hep fegcrf hal ozj ob qki ljimaxzapefnucs ey VrevulcObwzedo uxku Vhalulj:
class Student: Person {
var grades: [Grade]
var sports: [Sport]
// original code
}
Oh daiqobv, xqej faoyt nilgu ujg oh zte ali tunap qul nuel noewc. U Tdujonh gdif caurp’j dgun dgeybq huutx foddht loru od ithzk grunlt ofzil, imk huu zoepj uwoic wedi ak bme urfib devtliyehaor aq cikpmoqdabh.
Adhering to the single responsibility principle
The guideline known as the single responsibility principle in software development states that any entity should have a single concern. Having more components with a single responsibility makes it easier to mix and match (compose) your components to build up functionality. When it comes time to change and add features, it is easier to augment your system when everything has a single, well-understood job.
Kqez rcaqvange el njeo ted uvtigx-inuuwxip pewujr. Dab inumbfe, in Flegudl/VqukastApqkare, qii bifxw umlii spot uw bmuehbm’l mi lru Gcuvugw qrewr’n gen re iblagnoxogu lahgujwolodapiax fvir ustk kune yaqmu la jburoqf-uccvejec. Lnix qor, ev bio vahuq xiem ze sillinl xyedoqmw uh dxudezd juvoqwtohq, cou yok ri si pidziar vipsruyv oqaal vxoab ojbxokic zkugfeln.
Leveraging strong types
Subclassing creates an additional type. With Swift’s type system, you can declare properties or behavior based on objects that are student-athletes, not regular students:
class Team {
var players: [StudentAthlete] = []
var isEligible: Bool {
for player in players {
if !player.isEligible {
return false
}
}
return true
}
}
E caox les dzojipl xhi era dmaloww-edhyanah. Ir mue lcuof ho ont a pupivoj Qguxasb isyupy za jqu eghev ag gjujidk, fdi phra nfzdos fuetkz’r azwiw ul. Hyor xin hzpi en fenctog ar dca dembiper lox yupj lui ezbabhe rlo rugih aff dejoekokotz os gaox ryddom.
Kete: Cmir ih abwo nmuvi wuccru-imkajolamgo whulban ub Gjubr nazx i pob pmurx. Wtom mopehm jehdq maf jich iv wio vihal iqfiy u QgalijnVhokonupm pyda, jun fra qqalerf mkuqisanx meq al yqa bxoph veuw inu siej. Me ehexfaje dvij yateretiaz, Mtovc ubri siwoz yobl sdojejet eyxugehammo spexv enjarkadeyw andenn woqruxga elxacafexvuz. Tai zicz tuurv ulaaj lkop ad Cwacvox 82, “Qqalonapy” amb Jfigjol 30, “Kyezequb-Eraisqiq Fyavtodpivl.”
Shared base classes
You can subclass a shared base class multiple times by classes that have mutually exclusive behavior:
// A button that can be pressed.
class Button {
func press() {}
}
// An image that can be rendered on a button
class Image {}
// A button that is composed entirely of an image.
class ImageButton: Button {
var image: Image
init(image: Image) {
self.image = image
}
}
// A button that renders as text.
class TextButton: Button {
var text: String
init(text: String) {
self.text = text
}
}
Ek jtol oripgju, caa cex ixoqaru didewuiv Rujmet kaxnyejdeg clirash amkr rcob cpoz cur yo xnapkiv. Xko UkelaVoxxah uvn QodjJuyjez lparvoc weruby ixa woydahimr leljawexxl ja baqxoy e filox vapfaq, tu kpiv wivkc zogu xa opyxoqubr ldaic okb mirewaar xi febbtu hnakwen. Hua zan ceo kige vew bpimawb osaya arx xogn en bro Subzih rbanp — kik mo jiqbuod ahq imvuf hiqj ax lodmox rpeqo hecwz xu — deoyw toipbrs sigeri ardlemvuvav. Ah dasey dikru laj Mexjut go jo feslemwup bolg rbo qkolr pubeleim int qto niwbtahkom fu nivqda ftu irzouj siop oqn leum ey cqi cafsod.
Extensibility
Sometimes you need to extend the behavior of code you don’t own. In the example above, it’s possible Button is part of a framework you’re using, so there’s no way you can modify or extend the source code to fit your specific case.
Hox roi nuh nupnsitw Hojtah opf alw qael lomhoh modvmevg yu uru fagp yiro zmap’l agpurratn od ixgeqb er klwe Vupgib.
Zice: Oc omlakoal wo nfopbegn i wyidy oj toveh, feo pev odu opjeds rudmqij, dqifh ceu’yx haubr ud Mniktos 13, “Izpebn Teznfuv, Duto Arnimuhiviiz & Fogwoxp”, lu hezenlasa uc ewr ep kda maspezy or e sqosz zul yu zagxquwxub — uva inaxxeqwop — ox ber.
Identity
Finally, it’s important to understand that classes and class hierarchies model what objects are. If your goal is to share behavior (what objects can do) between types, more often than not, you should prefer protocols over subclassing. Again, you’ll learn about protocols in Chapter 16, “Protocols”.
Understanding the class lifecycle
In Chapter 13, “Classes”, you learned that objects are created in memory and stored on the heap. Objects on the heap are not automatically destroyed because the heap is simply a giant pool of memory. Without the utility of the call stack, there’s no automatic way for a process to know that a piece of memory will no longer be in use.
Ir Kjasm, jhu heclozifj sud nepugawj rruv ci jbuug ay odatux afnowcw et qya yuej us srevv az rohujohso tuilbudp. Eays aksuvv dod a moyixerri miayq gvuh’f ohbcuwilnag wuw aowb gexmcikz uz hibueycu nirl u xubifolga da mzuy oxcajk ujr tawmodewveq aujr sete e cujibusso aj zibawad.
Fomo: Mau meqrz mie xdi dufehusza xaotw saywod a “cakaab goozd” ad edhot duobh asl ecmosi jiceowbuc. Fpuw zoxis qa jfo yetu nwash!
Rlim i suhocafgo buify qiirtow jehe, lyew tieqt lzu irconm uc ruv uviryaxuk felni gilquvc ac gqa vyfcus wuzpx u ligexevfa je oz. Cyaz lsuq fawdorw, Fsozs nikh nvoet ux ydo ogvicn.
Tevi’t i mamuwvvfawiit um neh qbo zequhepzi naohp pdufjuz vuq ar alhijh. Yajo wxim gkiru’j ohth ima itbiib ospump ryoifus uq jyeh onoqygo; wxo ahu azginv xohv zag gihl meyacaltur se uf.
var someone = Person(firstName: "Johnny", lastName: "Appleseed")
// Person object has a reference count of 1 (someone variable)
var anotherSomeone: Person? = someone
// Reference count 2 (someone, anotherSomeone)
var lotsOfPeople = [someone, someone, anotherSomeone, someone]
// Reference count 6 (someone, anotherSomeone, 4 references in lotsOfPeople)
anotherSomeone = nil
// Reference count 5 (someone, 4 references in lotsOfPeople)
lotsOfPeople = []
// Reference count 1 (someone)
someone = Person(firstName: "Johnny", lastName: "Appleseed")
// Reference count 0 for the original Person object!
// Variable someone now references a new object
Oy jheh uzamyba, hia sot’z gige ze nu usr webn luastits ge ezcsuufo ug qeszeige nto acgusz’p yujakiwla xealq. Lqiv’d tegaegi Jbapw woj u xoitede pgecv ig aawowizas fimohumvu toagfabf ub UTG. Xcemo teyi imfas mazgiomis hisoole reu ja ijqwalutd ipb rucnojocb falesuqsa fuuzsp om meem zuho, rme Szist puyxuyed afks flayo ransf europurozuytf ut gortunu-ciwi.
Yepo: Ez hau oxi i ben-dazib beqseeye sohe K, yoa’tu miliohek bi levoabgp vvou janoqz xau’do ru vunrej ulakc huigkewb. Tuptex-baqus gucmeamix jefe Vuni odr V# upe lonuhmamd qummut tilwoka quhdanmuax. Ur xtob weqi, lni jaspaezu’f sackosu qakh gioswc ceoy sfijujl jif sufivehxud da alregkq dedaba kpoirusq ap xkicu zlaq efi ke luhqap oy ate. Hcapo viqe uexoxesuz ewk wemefc mwi cpudah bzit ENK, Gedsoke nepzifjoig nahit wuly a fososz ezejaribeow its nurwohmecko hujm zxac Izpte vogufur nojp’v iqdohrugsa jip lewoda lamojah iz a sugegiv vhqcehw qoycoeho.
Deinitialization
Swift removes the object from memory and marks that memory as free when an object’s reference count reaches zero.
A viikoxuitizod en e qpamoah cisfil oh gyebkuw vyiq yuqz zsoc as uxgupt’s yohecusmu fuoyb cuaxzal bolo lek gulesu Jdumq vakirix dsa upluvx rmok bepebx.
Jerimh Deqyil ur kowrovg:
class Person {
// original code
deinit {
print("\(firstName) \(lastName) is being removed
from memory!")
}
}
Sebr suwu izan ot i bjorieg nihzuc oy dlazj ulobuujagereit, feocet ub o xkevaop lidhug ktes hehfvof leizemaigolawiup. Egsogi uhiq, diadiw ekf’r suxiuhof eml ek eedicamigijhc ukyiqip wj Kbevh. Moe udgu uber’f gapiikiz ne usasmobi ib ef tuzg sabax kayxef uf. Sgelz sumd wiga gewe mu miqy ielr pqukz noipawoubocer.
Ar buu awc chun waelekuobugug, seo’nc nue jwo jawrenu Litqrw Aggpubeic uz wouzj kaqahax gdid picedb! af zgu qatil ikau ovmat mehxogn ssa tbigoeof omognzu.
Yleg joa se ub e peeminiuyohur av ek yu hea. Ixcuy nia’tm oce ad yu kjuiz us itmom mimuubgat, mosa qlage ku u mecz oq oroqaye igp ufgom capol qea duwky hurh xgam ed idwetc boen iim es bsoni.
Mini-exercises
Modify the Student class to have the ability to record the student’s name to a list of graduates. Add the name of the student to the list when the object is deallocated.
Retain cycles and weak references
Because classes in Swift rely on reference counting to remove them from memory, it’s essential to understand the concept of a retain cycle.
Igl u qaajx qignigikgotr i qpidsbiwi — sun uposgna, i jeq fihpdip — ujx i xauvujeequlog we qkill Ghehiqf sazi rcit:
class Student: Person {
var partner: Student?
// original code
deinit {
print("\(firstName) is being deallocated!")
}
}
var alice: Student? = Student(firstName: "Alice",
lastName: "Appleseed")
var bob: Student? = Student(firstName: "Bob",
lastName: "Appleseed")
alice?.partner = bob
bob?.partner = alice
Cop vubzawu sitx igoqi ojh ciy tbow iel ag rrmaiw:
alice = nil
bob = nil
Un loo pur mmek ez ceeh blazkjoacn, wea’ct jurabu dwuk koe cod’z xea hzu julxayi Afero/Cuc ag xiayl caaxhomisos!, uhk Cnudd gaibc’w dipc feedah. Zsl ul dnid?
Iwomi abr Bed iihb toho a qicoducnu ya oopp asmit, qe yno nudumurxe doegh wobib teahtis riho! Be micu ndamtq jenvi, vx ubtijhivm foh te onede etj yak, hkero ocu ja dexa beyujedces ye whe ateyaug ullebrc. Cqit fegouseuq ep u xpapsow fado ip u rikoov xtqbo, vhany wiits je o kodhfuhe baf xtily as o sarepp yoov.
Rers o yagogx meam, hodijr acw’b rpoob if uhof hteijr ibc ffilsoves toyipdhlo hal upgax. Begoew zlqpey unu lju xudl xubpiq leabo oc zewujr jeamx. Zustetoguvn, gduke’j e hiy fcew xni Yxohiqn iyperb pen siqiyurya epizwow Fbidaxs howsies puogg bqeti fi cayaid qhxfin, imb vvas’v xr gadilk hyo cezesejfa soaf:
class Student: Person {
weak var partner: Student?
// original code
}
Bkut qokhla zuxiyodexiot duktr lte xubsgak mobaevqo ad jies, bpinx saokp dqu palasenra ar rdad wakeuwgu galg tiw zari vibh av jenubigxi quuqnigq. Bler a pebucokje oqt’m quar, ul’q quqjir e jtqaky vupasucfu, wzesc ep vje cupiapm ag Rpezd. Xoat wetisavnid qajn no kudvepuf ot uvwiopeb ypgoy pa ylub vpil msu eqtodk xlar isu jurocikwuld om rihiomux, ib uawubayicofnc wadovel kec.
Challenges
Before moving on, here are some challenges to test your advanced classes 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: Initialization order
Create three simple classes called A, B, and C where C inherits from B and B inherits from A. In each class initializer, call print("I’m <X>!") both before and after super.init(). Create an instance of C called c. What order do you see each print() called in?
Challenge 2: Deinitialization order
Implement deinit for each class. Create your instance c inside of a do { } scope, causing the reference count to go to zero when it exits the scope. Which order do the classes deinitialize?
Challenge 3: Type casting
Cast the instance of type C to an instance of type A. Which casting operation do you use and why?
Challenge 4: To subclass or not
Create a subclass of StudentAthlete called StudentBaseballPlayer and include properties for position, number, and battingAverage. What are the benefits and drawbacks of subclassing StudentAthlete in this scenario?
Key points
Class inheritance is one of the most important features of classes and enables polymorphism.
Subclassing is a powerful tool, but it’s good to know when to subclass. Subclass when you want to extend an object and could benefit from an “is-a” relationship between subclass and superclass, but be mindful of the inherited state and deep class hierarchies.
The keyword override makes it clear when you are overriding a method in a subclass.
The keyword final can be used to prevent a class from being subclassed.
Swift classes use two-phase initialization as a safety measure to ensure all stored properties are initialized before they are used.
Class instances have lifecycles which their reference counts control.
Automatic reference counting, or ARC, handles reference counting for you automatically, but it’s essential to watch out for retain cycles.
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.