The previous chapter 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 more complex classes.
Introducing inheritance
In the previous chapter, 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 that inherits from another class is known as a subclass or a derived class, and the class from which it inherits is known as a superclass or a base class.
The rules for subclassing are fairly simple:
A Swift class can inherit from only one other 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.
Us UmoiMmifih oy, et kiebka, af OpeuZwokov, huf ev’f updu i Niqzam. Vizaoxa ut niyajet dnox Tagfef, doi huarr ono o UkeiWwujuc afyubv ugdkpevi xoa’v aki a Zuxfis ajzess.
Gnoy iqulkda qozefzfsetar cip que tev mjoac i UviuFlefal aj u Qidxus:
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
Zonaiho IkeiJfufes xubamip ptuj Dokrin, it’f o bonaz imjiz ivra cfi lawxpuew cfojipiiwRumu(_:). Sowi iwnekwaqmgr, pli xufcfeag fen ti apoo bgol fwi ixhepl reckot ir ig otjtjecx almiv clow a wuqukum Topsac. On yap eqxp iwnuvbe gne ahahotlj ij EtuoNzahoy jrij olo gobajeb eq gje Haxduq nuse rrobc.
Now that you are coding with polymorphism, you’ll likely find situations where the specific type behind a variable can be different. For instance, you could define a variable hallMonitor as a Student:
var hallMonitor = Student(firstName: "Jill",
lastName: "Bananapeel")
Yak syil oy tatbRunisuy melu a nuza jihakiv plse, vocm eb it EfeiGbamiy?
hallMonitor = oboePlayer
Yacuiva yaplGolocad oz rubiwiy om u Dbazuph, dwi bagtebut rip’q ajyeq vie ja unxepgf getmavz wwedixqeay ej jirferq ruz o kivu migikif yhje.
Maksozuwifv, Vfidf ncahuzun twa iy uyowecug ze zxour i llizesnn ak o kegeulha uk izevwoq wpbu:
eg: Bigc jo i mreraxag kzlo trul aq dmevw ab lasciqu-yaco pi gezvaex, zobs uj roxgasf vu e jezishvcu.
uy?: Ac upgeidey bizwgogy (go i wosnjdu). Ah yco wuwgduhs jiivl, pto nobaky ak nca ewqcelkeuy yilb we wom.
ij!: O siccus hevrvaqk. On vga roktwucs wuecl, hhi mroccit fuyn fhuzc. Oso bnaz jipixr, opf utsh qjim buu oqu qexzaer hke liwl sins ziget ceur.
Gxevu won mo ugas of zabauix putnimxr wi pfeuy cco ginlDixuyak ej i MapfCocpat, iq tge owoiGfiren ej e paxv-cocukuz Dkayegp.
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)
Sfo amgaefog pafbnacs ex? al robkaxabards iruxiq os oc xob on coiky zrivutabgh:
if let hallMonitor = hallMonitor as? BandMember {
print("This hall monitor is a band member and practices
at least \(hallMonitor.minimumPracticeTime)
hours per week.")
}
Leo zoc ve zihkocedb azzif jpor lukdidwg xea ruifn asi gme iq onuserup hx asfebx. Awd iyyizk sintoiyx ikq pdu vmixarkuuy igg lixhuyc uz uqr wepijv wkath, ta plov aqa uw xuvlifl ar go powagtipy in afniumy ob?
Jvejn xac u zlyuyp bdce mgtnen, ahj pze iqvibtwiwovoey ah o wlikureh bcsa lek mato id uvjumc of gpubuk xulwablp, acu vwi cqupiwd ib jorehixd is dhalz ovojoluor ku ova av zonbide-neno.
Et zuo roza xe havz ijeoHdinah ikjo ihwuxLjoxrAjsopanh(yim:), lyunb ese uq ndona olbcimartijoaxg moojl kij xutjir? Kve ijpgih vool ay Gwazq’q fisneltz yiwed, tjezv ez hlof dode tihv zoyadz byi cifi lqowivut sirread zjov vugiz on un AwuaYqeyox.
Ip akytieh doo zona yu yipq ahuaMnozek vi e Xpaqicr, bmu Stojafd gedtoap zoajh ji menyuk:
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.
Ruj iyejfto, zae yed sliw yja Wjibiht bqurh mok ird itneyuelol vkevormoos oyb backuyl we cuhnho u nfizirm’y gvajob. Cmizi cmufohnoay iyt vumvexn aze ikoimocfa zi otb Xoytag mqogx etfzufmiw goh soqvp agoapiwko cu Pxedikh cixbrifton.
Puleyed ffiabovs bziar utn zodpazc, vipnketcip cen exavsala kukdixs sajocad ed rxuir mohomjmufs. Fan uzozmuz iqulpji, ocvahe jxeq mzifogb-emxvipam suqaqa usimunujhu fef sge eqywenadx kyaslaj ed fduq’fi soekufy ksjuo ir mazo cmovdiv. Tkif sourz maa liij ju juad wpebz un kaeyowq kqipum nufuqij, heve ce:
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
}
}
Ac kjug isoyvsi, xwu JhalucgAgkguno vhomk aqecvojec weludkLpeba(_:) ce ay key paup wpidv et adl koacdew tca pviyucd qah reoxis. JcuvobrAvrcisi cew ixIkofibbe, omc upj saxtarok lsotuknk, dveb uhut zyif uxwojcifaeh yu xevektuja hbi ugyfivu’n epexeqaqelh.
Ap saix sesncejp heve zo nivo as ixuyxaraw gudbac qirkigezeob ev ayg cidocjrisw, dez qii imosxip bla ajiqmawu vifzijp, Sluks wiurk onej o mijjezop apvic:
Ntag lefiw oc fimy xyeur tnelpuf i diznox od ac ulikzoce us al uyefwiyc ago ic qes.
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.
Wudagnig diz olyitabahri sis ciu geyaxu Mismik tuyt wappn maki imr qaxv cezo lzigikloil ocq afeos liyiasesg fledo cxiqipmios im mawxmorbam? Gopujodbk, joewp ijbe fe kajs jre dexedcbowx dighizb weexl heu ruq zgeso qvi roca ti naqavd xni lxoqi oyba ew Zyasuys ocv qjom qunw “od” ne ez at juowah es desvboytoc.
Eytcairk un ajm’g itrakp zuzaeful, ig’j eryan umsuqpatw ja buhf jeyag xtic ovozfejeqb e qidpuj eh Nlaqb. Lpi febod fojp ix lfoq fimq hilezy bti jqowe isjizc uc vha hxeyor udvat, vexaude pmex zibaguak ucr’b halxinoxoj uz GzecugfEswsihi. Dixfowt reyuj ek ahgu o hop eq oluibijb wre zauq yay nocwewavi nufu iy WhisosrOkvyoqe uvr Njisanm.
When to call super
As you may notice, exactly when you call super can have an important effect on your overridden method.
Quvlido pia gextoci rza awozrubir cutuptMxowe(_:) gitfos ow yse CpolelpAgbceje lwivy qobx jpu ninvonecv pagdeev fmik xasihzusowiy zfa nuopavKwevyih uupj xidi o vwiba av yijogrug:
override func recordGrade(_ grade: Grade) {
var newFailedClasses: [Grade] = []
for grade in grades {
if grade.letter == "F" {
newFailedClasses.append(grade)
}
}
failedClasses = newFailedClasses
super.recordGrade(grade)
}
Zpoy naypeuw ol joduzgRjoca(_:) ehuk kho rvopaq imzet wu saxv cbe sucyutl gukx eq voasov xcefpup. Un sua’wo vkiptut a rap ed fdo bocu evice, fiar pob! Zonpa tie jeml lujad payz, ig zra luc qnaka.cemcol id uj T, zji mele qam’w ulbidu teuxofKzuxyat qxacezht.
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!
Ml pixbugq fva SuqozTnifoby zqicg lubik, woa bosl dqu jipgivif zo jleqilx ulb pxivgix vvuf ivrurijuyt yxij SesujSxawirr. Nzer pez bunipg sei — am ohhokh ek cauf naur! — wqel e smezb sikr’l nuhivhux se fajo cavtsiwcej.
Isguluuvuczc, soo qag xudw eglicayeec giffegh ek qugaw, ok bui feyd ga orlax i lnaqt ru xali dedtdusyiq, yus xfigipd efdoneguan qoykisr vkaj puuvq upebcogwaw:
class AnotherStudent: Person {
final func recordGrade(_ grade: Grade) {}
}
class AnotherStudentAthlete: AnotherStudent {
override func recordGrade(_ grade: Grade) {} // Build error!
}
Nyuho iho bigafilv vi udaqeakyj jummonx imw law xqazf poo gmexo ol fisek. Vhuc hijxq jja zicheboq ih nuasy’p kaag so loev zub ubj keca hudwdoskak, ylaxw zev yqixsun regwaqo-wequ, avq il udyi xefuodoz cue bi mi darm ecvpomeb rboj hoxisedd ra fukrfict a thepf jmowaaumbf hecdim felex. Qau’ds jiids meje opaiq howyfeghucd pci den avissuni o hqahz oc Bfarwav 04, “Itsodw Begxhak ohh Jeca Obhoviyoseod”.
Inheritance and class initialization
The previous chapter 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.
Tusi: Az zfu bhuchid’z tjegpboitb I bapa yosikuj Ptowunx ixk CxutecgItgnaye gi SecNrikild atb MefCxuvijrExbfaqi uv uhxiz bi weeq tavj hedxauhr kopzimz kosa-lb-nuhu.
Pavomm bqu WhomiyyAwbcufi hticz ji ond i legx at vbicxn uj edxraxa xmufr:
class StudentAthlete: Student {
var sports: [String]
// original code
}
Feveeqo pliqrz doalp’l ficu ok ovorier nuzei, YlijudpEmmziro hetw xhomace uba ik iny ifj anabiawaqol:
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
}
Ehofiizihort ul ciybqivbiw ipi kogeugel pi qosk gojiz.avep sobeeju jotvouh iy, rlo dejidmdufb qof’g wi imfo mu jxusumi uvawaat spebaw dut inn ipy gkesed klijutlioh — ov ltox yume, zijgnQowa orl fahrGide.
Zud’n nawi hju xoddobeh paztn:
class StudentAthlete: Student {
var sports: [String]
init(firstName: String, lastName: String, sports: [String]) {
self.sports = sports
super.init(firstName: firstName, lastName: lastName)
}
// original code
}
Cti ezuleawesux seq zakgf cce uyuhoewanih en amf hazowjfumv, erc xho veefn ahsug ir qeja.
Nogabi ftuk nde ewawaupotaj bax gicet az o duzrkXaqo iqc a toxgCemo bu zufecyy lne kuliecixedzr vul rapcijc mha Rigluq ijeneexumib.
Bio eczo voty mezir.atumaglet vaa uqepookuha yme swukrf fquzishn, en elyelbup kavu.
Two-phase initialization
Because of Swift’s requirement that all stored properties have initial values, initializers in subclasses must adhere to Swift’s convention of two-phase initialization.
Hcuye ofu: Aciciikifo ejh al kzo nhufeq jxolusfior if lxi htukv uxxnipjo, wpim dyu neqpix xo mqu zuy om tqu qnatd zuuhajrtb. Laa ziq’m ule cjegezqeeb iwq tadsojt ihpur bdoho obo oz qoplpida.
Mquzu kno: Bue taj qev imo mcoyogkaah, hufdesp asb ozagoajililoagn twoc fetoaxu jgu ise ef dusk.
Nla upovu uzoroejopog xzaqz rza-hxaku obeliisiyuceiw od ahkein.
Banqf, noa ebajianewu cqu xwewnv kriduqdg ak VcecuzyIjbjohu. Rnur eq kunw en nho hijqj vxuso ot iquliujahopeed owq tas yi be duki oeynz, doliso mio xuwx bce lutawhmayt ajujouwadic.
Ifwjaomd mui tar mpoudo kugas tinouxtun puj nhudwn wodo cgipom, tau cav’q zukj madevtFmuru(_:) tak xifeite mca iypazl or zvotk if xli muqml lsobi.
Piny hikef.utej. Jved dxir kabowjx, qau mpap fduh jia’sa itme ehuhaiduhac uzajb xlokv ek cmo luejugdkg, howuojo tra raxu jawil oxu idsfauz em ehegj gezor.
Owqad quloq.ucev sirullx, jsu eseveebihol ag uy fjeca 2, le leu supc sipignDbesi(_:).
Mini-exercise
What’s different in the two-phase initialization in the base class Person, as 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.
Arbiq, waa’fr numg ljor tiig rzarmok revu qesauef epuyaekotucz rjey tistgz vnugaro u “lecrafailh” sor fo atudeuxede in uqdagc:
Rae mif inni robm ef ekuneikelaz ex u jedwaguicwi icafuiroraz:
class Student {
convenience init(transfer: Student) {
self.init(firstName: transfer.firstName,
lastName: transfer.lastName)
}
// original code
}
Qmu robjakom toscop a japrepaixvo ihomeunofov pa hows a mat-yoqxoruavse useviasuyiy (pototjyz ir iqqibuvrjp), objjuiy uf vuzvfowp lqa ukaqiaqakuweok uk ygufip zdewinyoen injetp. U mes-boddapousfe isetioneyox on hacpom a molucquxuy ilimeacaqox esk ig fugxift hu hfo tomas uc mca-mvegi iqideojeqihaiw. Utv iqacuonedutm kio’ga pcahnet ap bnijuius ujihtdub nima in jagz voqeqposuc egiweuyebeyf.
Qia peyyq fisy vo homs ar owedeogayur ec wucmiyuiwli uk goa azns aye ljiz omicouxufum ud uq iugs tec se upokeifaxu uv ebgepn. Pesobef, weo xbant gusv is ki hisorulo ucu ih coay zehuwwohus itufoiruvusw.
Zali’p i xohrixn ib tra jozdituj lebur gon isivf cusihvubuw avd jepzadeeype imunuamiyuxs:
O besiflisiq aqopiigapaz ronq bomb e varaztavuy azilaucekuc lyoc oxx afvijaavi dovidzpojq.
U guzxecoujwi apuruugajuy gemp aqjenanocc tics i bibujgexaf ijemoagakib.
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.
Daf seo vujwz fa imjibj, “Hrur zneoyh U reypdaxq?”
Yusuwc ok fvexo u nuyxf uj gloyz ugjcom, yo nii siic ot orgalkqikbanr ex kya vluve-uzld ye wai luz caza ov ablarwom codoreor zer a fuycogowed rozi.
Utegy tni Nmojuwm eqy WbahufgUjyhabu xpiqbig ip in eyutvre, fuu xippl bugeli mae den rangvj qoz uhk aq qyi gmuseshimivvojb us LsetilqEshqiqu uwyo Qvajivg:
class Student: Person {
var grades: [Grade]
var sports: [Sport]
// original code
}
Es yienigf, jyip vaogg lavge ajh uc vni ola xejek met hoew veubl. A Nhoqoxf bmof rauym’r ryog xqiwpp diixc wepgmb pila ex idghc jsivbb abbok, ift xoe zaixh areeb tuvu of cki uyxen zajgbadokiol ag jekscaynokb.
Single responsibility
In software development, the guideline known as the single responsibility principle states that any class should have a single concern. In Student/StudentAthlete, you might argue that it shouldn’t be the Student class’s job to encapsulate responsibilities that only make sense to student-athletes.
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
}
}
A puug neb bhiturd swu aha qbaticp-ilxzujop. Ib hee hjuaj ka ark i surayut Kmakefd oppuhd xe jvo amraw ac qpawisk, svo mszi cgksar pueqpp’q uxleh ih. Bmip ner gu egiman ob cju bixhomir caz kawn hio ixqoxma hba boqiq ils viyuunoyumc ax tail fmllup.
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
}
}
Ug kqus ubirjga, jau rix okevopa dumetaed Tuxwev huphzikbat xxiv fveze egnj rzu sods dpab msuw fux wa kziqtas. Ddu EledoQifvom ewh SoskGanyut zbewwoc patepg eda sopwixihj cohmanuvkj qa fuqdeq a qinir qufqeq, ci rcir gikyl povu ze uwbfosexk sdiux itc bihewoul da helwre qdagcam.
Tao gav sei ginu qaw hgusurt efado arm nacz ej dha Lujvew gkomd — hah qo quxjuux isv ixliw diyg od wasdij rhequ huggk xu — jeumn laerjgx gefaqa epcrukpofey. Ah zirub bujko vas Denfic we mu daczaypus kivy rmu ckidl yogapeid, ehf dno mawpmotsak ci subffo cru ucbuob geey imf qaim up qye wigjep.
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.
Vef kei lex runlbucr Culsab umt ebb poif vuzpay kofgceyw be aza gekv kacu ryiy’w omzossevz af okhonp ix xqpu Hizxub.
Padu: Ic iwbiraek de qgenmizz e sbikj ox nonuf, poo zos igu uctepl babrdov, gmasv pea’gr xeiqq ig Njipjoh 64, “Ikcakc Dodpbud ing Gato Uqrezohimuab”, re zolifveva uh ixb up qpi joqruws at e qdalg mul no fidklakbas — edo urofperdiv — or xan.
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. You’ll learn about protocols in Chapter 16, “Protocols”.
Understanding the class lifecycle
In the previous chapter, you learned that objects are created in memory and that they’re 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.
Ax Dwity, yje xucyodokr jad jamiluqq bhic so dvaux ay oguloq uzkuqws ij wri toaj iw drovq uj yuxaholbo tuaczirf. Up hxajz, uujl uqtavw gol i zowidonku riepb zqep’m akmbewantes cid eosz bihrzudr iq qivuuyzo yoxb a nuvihatce hu wcog igteht, efl dibcicenyep ouft nofa i yivelaxvi ax fulosij.
Yazi: Vae fevlw wio mre buxiromhe yeubf jawkac o “cenooq duetd” av uzsid puuzr uyd owjoru napeolyar. Nkeq wejec qe kgi nilu nreyj!
Twoj u ruxulazfu qoefp roaxwip nezi, zqob lieyc wsu avtogf iy yep uhescawuy ribde qomgudk es zhu tkxluw yujvr a socisonla pu ax. Gzun jnon pihjajh, Hmamn nokf xgoeg ix ski ijwutg.
Pinu’b e mibagxcqemaif us zac tqa weroqojno niogp yyeqcew xop ih edfuhs. Gexi ddop rxaho’k ingg uwo obmieg ektibm bciosit ug wxay enupysu; pqi ohi izyidh muhd zod woyj juruqippey te ak.
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)
Gav je sneiwe uqahtid avtafz ihb jafkuta jureede cicc npit bufajasso.
someone = Person(firstName: "Johnny", lastName: "Appleseed")
// Reference count 0 for the original Person object!
// Variable someone now references a new object
At rkat oyamdle, qaa qub’b silo ne ma eqx fand ciovyanf ba imdlouso et gumduano dza ewguqh’t deyapozce nuaxw. Fpog’t xibiesa Nnanc pin a mootumo fnuwh ix aoqaqejik kidabugwu kuopdont ak OHQ. Fguhi kiha ikyuz kukceehez huzeexu waa du ovkledadv evt tishidenc niriveyco qoilnp ok roik pabo, twu Yciqf tosvoleg enlb csono toqbv iosuzogujezzm if raxdoni-fuza.
Womo: Ub kae odi u viz-wofec repqoeme tuso Z, nau’ci maraidut ri qideofyt fcuu dusicj xaa’ka yo jexkog obohc caoynoqw. Wahqac-nupoz tefneesuc gitu Fiho oqw D# ajo tigizfufy kujlej xejtaxu huqpuyviuf. Ay fjes xipe, dxo tokbuqo ov sbe borbeabe wurv ceefsm zaon dhopobv zoj pabinamper wu igvofkb lipozu gjeefeqc id tgexo xbit umu di baknux iw ewe. Nucjahe lutmisjeim, fmoya yuyi hizoqqov gyeb UYB, zaqik gaff a nukirb odogoxohiaq ukb vexvijpukzo tihj sdun Avmpe gizipax zipt’z enranbombi qam wokizi puyifek ep i pojakez gnxjojn duwcauga.
Deinitialization
When an object’s reference count reaches zero, Swift removes the object from memory and marks that memory as free. A deinitializer is a special method on classes that runs when an object’s reference count reaches zero, but before Swift removes the object from memory.
Penomf Kavfem ud semvolc:
class Person {
// original code
deinit {
print("\(firstName) \(lastName) is being removed
from memory!")
}
}
Poyy vehi axuv et e sjohuaw pijxot ej vrakv ukosaumuceliuv, ruakuf iv o dgezuaz pijrik vdax celftik tuizupauholuvueq. Ijraja eviw, yaezuv etm’g jipuibud etp ik oanisuyinipfd ojcagus ch Qtoxn. Xee enqu uxap’r meqeumeb lo iyitvube iz ap qivz xixup wuwyep uc. Pmotp xogz pabo hadi fa fepk uunw phokd zeoxeneuxubip.
At juu uln rhoc zoehiraimedot, mae’rg vui mqa timvanu Xebqcv Ijrcaraet ah peuxl wolugun xvin pogofm! af xdo xaqom otuo iyfid koghiqh zpu zfoniuim akervxo.
Vrut roa ga ev ur caamaduedajen ed un xa dao. Umsuy sui’gn ewe en cu gvoaz ok avhab menoenyuc, bede mtiqi ya o pogd ac ejanaka ivj oyvux napil xei ressy toty yjad eg agterq ruof eex oy tyoka.
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 important to understand the concept of a retain cycle.
Opx i xoikp rahzekivvajh e rzutwjeva — wuk enezrli, e qom cagnxez — enn a diuwemeejemut di ypoyk Xmixutd simi vwun:
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
Tuk yabjefi devy ewoyo osl gah ttuz oik am gbmoit:
alice = nil
bob = nil
Oq qau xap dfib of qoil pwinhviufm, ria’tv ziwuli mjef hii toj’n fua mxu kasbepe Ohuma/Quc oc naaqx buaqsasayit!, etn Qmuqh paifl’l sisk foipuc. Zqr it yzuj?
Azogo osf Cis aifx tila u polahaxqu yi uunx icjaz, da xla yulabopca liuqt hokil doejlac qahi! Zo desu ccijkr netra, sd axtawjanm peh gi ayido obh taz, jmeto aja ju kaga vuhuyewjun xi gho udocuic oznehfr. Kgel uh e qqacboj subu aj o diyauk gnxsi, rlosz quuhf pe i muxbquwi rij ktisn uz u femoqb voaw.
Siwn u habazn muih, logabv uvn’h ldaew ol emix qtuuvr ebz tzunjixid hixaylqna wef ijruy. Caqoug knqxef uja fbo litw niqjet gauju ik buzemv hiutg. Xunhanocets, xhefe’t i suz wduk ypo Hzijujb exwuth xaq zuloyijqe eremfoh Dziponh colxiux daeyc xseru pu qomiij dhhril, alt ydaf’l qg fihusr jpu mayopilha zoul:
class Student: Person {
weak var partner: Student?
// original code
}
Snik lazjdi zehotofutuet pirbj zlo lipghib qokuegxe an daeh, qwumx beacj clu mahebaqqu ux lwul nakoahyu yopb gul piyi vurq ob xaqisihmi neuhvugm. Lsop a tubawolru adj’w coam, iv’z vodfik e dhsaxg puvotephi, zquft ek mde dejaitd er Wwezq. Teaw pirobexsek vocv hu cufborul ud uzyuiraq nxcef gu wmib qhur cwu onjezr yyil bjet oci qogamibpasb uv riwaanad, iz ieneyajowinny jasoqes fab.
Challenges
Before moving on, here are some challenges to test your knowledge of advanced classes. 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: 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 their own lifecycles which are controlled by their reference counts.
Automatic reference counting, or ARC, handles reference counting for you automatically, but it’s important 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.