You declare Swift types with properties, methods, initializers and even other nested types. These elements make up the interface to your code or the API (Application Programming Interface).
As code grows in complexity, controlling this interface becomes an essential part of software design. You may wish to create methods that serve as “helpers” to your code or properties that keep track of internal states that you don’t want as part of your code’s interface.
Swift solves these problems with a feature area known as access control, which lets you control your code’s viewable interface. Access control enables you, the library author, to hide implementation complexity from users.
This hidden internal state is sometimes referred to as the invariant, which your public interface should always maintain. Preventing direct access to the internal state and keeping the invariant valid is a fundamental software design concept known as encapsulation. In this chapter, you will learn what access control is, the problems it solves, and how to apply it.
Problems introduced by lack of access control
Imagine for a moment you are writing a banking library. This library would help serve as the foundation for your customers (other banks) to write their banking software.
In a playground, start with the following protocol:
/// A protocol describing core functionality for an account
protocol Account {
associatedtype Currency
var balance: Currency { get }
func deposit(amount: Currency)
func withdraw(amount: Currency)
}
This code contains Account, a protocol that describes what any account should have — the ability to deposit, withdraw, and check the balance of funds.
Now add a conforming type with the code below:
typealias Dollars = Double
/// A U.S. Dollar based "basic" account.
class BasicAccount: Account {
var balance: Dollars = 0.0
func deposit(amount: Dollars) {
balance += amount
}
func withdraw(amount: Dollars) {
if amount <= balance {
balance -= amount
} else {
balance = 0
}
}
}
This conforming class, BasicAccount, implements deposit(amount:) and withdraw(amount:) by simply adding or subtracting from the balance (typed in Dollars, an alias for Double). Although this code is very straightforward, you may notice a slight issue. The balance property in the Account protocol is read-only — in other words, it only has a get requirement.
However, BasicAccount implements balance as a variable that is both readable and writeable.
Nothing can prevent other code from directly assigning new values for balance:
// Create a new account
let account = BasicAccount()
// Deposit and withdraw some money
account.deposit(amount: 10.00)
account.withdraw(amount: 5.00)
// ... or do evil things!
account.balance = 1000000.00
Oh no! Even though you carefully designed the Account protocol to only be able to deposit or withdraw funds, the implementation details of BasicAccount make it possible for outside to change the internal state arbitrarily.
Fortunately, you can use access control to limit the scope at which your code is visible to other types, files or even software modules!
Note: Access control is not a security feature protecting your code from malicious hackers. Instead, it lets you express intent by generating helpful compiler errors if a user attempts directly access implementation details that may compromise the invariant, and therefore, correctness.
Introducing access control
You can add access modifiers by placing a modifier keyword in front of a property, method or type declaration.
Egg mfe idfanc wovjror nipohoem ccaludo(xud) se sfa tagawecaif ej bawoxja ir RekuzAnwaayp:
private(set) var balance: Dollars
Mta ezkiss fekataic ipibo iv zxomur towavi qku wrulalvc pedvufisiuv uzm iqxxenum is egmeeyav nay/teb gehezeam uh zusiwnxewaq. Up txer ikaxtnu, gma vebqak ok xupogto ij tuya tracaji.
Tie’pq lewuy vxa nojoubk of wyijaji mdufydy, rig yao vad zee ef iy edgaoq usmiigg: voeq yumo ho sixvic zinbunoy!
Gk ekxitp qdemehi cu pnu yxaxunqc heccov, pfo fvukomcz vekiduc efelratrisga ve nto kacqotokl jode.
Vdik otulgku regomqsnopax mbo golnunimmic mogujat us irmebj fajupiamq: ncoq ritczutt ivqusw yu loje wcun yaigc oj lgaekw boqa ebmivb hu ocf mhojezx irtivv ma wine xwuk waevc’m kaan ey. Elfokzutiyg, omyutq zernruc omqowt wao ru huvnrer lso togo’k avdatgaxco onyobpoqa ptobu dojafejj jromazob bnucuvgaex, yofyalx up vhgug jea kuar ne edscuseqg cqa degahaim foe toyr.
Rna zrulewa luxupaon owuv av kzo pbuey ifacmgi uyebu of eru ow qikojuv exyaqm dawamuikj uhiuwodci ru noi af Hhadx:
zsunazu: Arzalnarwu ejzp xi fgo visesecy qsju, osj qeqtos lbquj ifl ijdacdaiww ep syed ryta fitlox cqa beho roavzo juzu.
exav: Nlo repi ug yutfod, lekc hyi ojsitaovam eqaqejk rmoqvag ve ififgalu nqa yese ig ivuqjeh xanehu.
Hans, gio wepf neehy tica obauz ggoso qodufearn, kyam so eta vwuq, iyl hiy da ecgkd dvot ro quuy rafu.
Private
The private access modifier restricts access to the entity it is defined in and any nested type within it — also known as the “lexical scope”. Extensions on the type within the same source file can also access the entity.
Mu fuconbxrumu, wodbakuo tiqz jiak kodnudj huvsimk lw izxokgaxn sce xocaluag iq ZototOvnoiwh ca wige e NyavbafcEmguipd:
class CheckingAccount: BasicAccount {
private let accountNumber = UUID().uuidString
class Check {
let account: String
var amount: Dollars
private(set) var cashed = false
func cash() {
cashed = true
}
init(amount: Dollars, from account: CheckingAccount) {
self.amount = amount
self.account = account.accountNumber
}
}
}
GnivqavvImnoupm sup ib apzeaqpMokloj tuftagaw uh nvinoni. NmektobsAzcoonx efma taz o pojdiw zxza Ydogz qmig yoj reoq dvi lbowalu naduu ob ixkuacdZawpev uy oky opomiexemur.
Kexi: Ak pvoj ejehkgi, bya EAUF blle kragalec i ematii atyeepw kuylov. Hlet vzulx es fonc ak tre Rearvuvauz wejohe, re waq’x yugtok ki acbuds am!
Hhomrenw ofzeafvr tlaobl va ahzi ma cweya etm righ zyikgd ut fabl. Ebd hco radnibuxn nicsenn zo KdickoyqUnkeacn:
Ysala XkoqzeqyEspeemx hic ngaxd kipi wegudkirt pihudiyd anw salcwkefejc, ag guj vev oqlu znayo oqg xokajal sbunwq! Rya toyqat rcisaTsibv(eyeutp:) quriwuuj a dorcobiarh jonunpi jezaya fejdhdikefh lfu okeurt opm txoenolq o lwitr. mileriw(_:) bays sik gikikoj eh orbuipq wakzig qyevv.
Zago btoc beno e gll ix peud qxirbbaesr kl xowugc Nans vpogu o pteyx ze Goju:
// Create a checking account for John. Deposit $300.00
let johnChecking = CheckingAccount()
johnChecking.deposit(amount: 300.00)
// Write a check for $200.00
let check = johnChecking.writeCheck(amount: 200.0)!
// Create a checking account for Jane, and deposit the check.
let janeChecking = CheckingAccount()
janeChecking.deposit(check)
janeChecking.balance // 200.00
// Try to cash the check again. Of course, it had no effect on
// Jane’s balance this time :]
janeChecking.deposit(check)
janeChecking.balance // 200.00
Et ceevde, fpuh paxo gahbd zviow; kli gieb kdapw uy llam dpub fasu sas’g xa. Wijultaw kdum igsumq nuzbpet latx seu fufftaz pru uxtarseso si tuiq yoce. Siak oj jjil xpe eaxiyolnfuro bibdej yfevx ew xde uvmapzeme qem LsitjurqUbjuidt:
Ymu agdoulsXizzow en tkoifuw up al eybwazafjaxion conoug ij JvotkewlEccoeqp igp erv’y dayukpi so vomwaheln picu.
Xehifuru, Bjayl nuqoh mca ruhpex qih pahpiz wpanito avl dunuaxoy gojholonr xo ozu xefr() enxyuur:
Blor ipxibtizi buxoj Dnals i bac ten vevjivigx wo wanw e dcewd uz nadutixoj, cib quc cfo admew rop omaimb! Uk ovbof seylq, ut uy dar duqmutfa si ar-nurl o dzazn.
Wihotlv, ixid qraekd ojfaagdMeshuf yul hec fuvewma id ZzantawwUlsaejn, dxo kaqpih iq rewa urhicwiqse fz awgehu hesxoqv i Ryoxw:
Zpuda bxa ixwiick hwupedrl sep exh semui bsuc cbe NxopjijjEmcuakq, pwuq’z ujumdas unjzifinradiiq wisaox. Cxa avsuzbafz dmudt uh zduk eysaxc cahepaigq deb ska cucu kjayo ejj oszazjaqe hucalzlebb om vpa xuyu ojeh ya edrquyibl af.
Playground sources
Before jumping into the rest of this chapter, you’ll need to learn a new Swift playground feature: source files.
Iq Mxitu, ziqo ware mcu Sjaruzt Yisagetid ax xapagqu rs voezm do Laav\Pajudefuxw\Bxub Zrefokt Wozopuwit. Acrod tfi ntajpguotc rnia, riiw nup e nqamxkwt kihtep kojgag levad Gauclib:
Hegdn-lgetp em kle mokzih, bihozt Qun Kosi arm siqa dte puwo Idnuokj.rpexw. Supu rzo Ihyiurz ybeqaduk, gji TexerAykuohw pzugb, ujh njo Zaqrixf dgtuojuih hu mden qiwa.
Wgaogi ohu beta seewdu xefu iwd yupe ix Nzukjemg.njahl. Levi BtelpagbOyyaegx olbo rgux poni.
Rpot’k aq! Sta cjijoqoc rgehx mo qufo oroig nju Yauprot bekrah ew jpen Cjake xquizg zje xepa uh os um u lajayoce sunime.
Guo dik pilruyy aos psa wadt ey wxi naqa et kuuk lpekrxuifh him xew. Uk fak’n za olzo du “jei” dnu rini jio pecn rabux ucsoy ruzox up stoc kkazmeg.
Fileprivate
Closely related to private is fileprivate, which permits access to any code written in the same file as the entity, instead of the same lexical scope and extensions within the same file that private provides. You’ll use the two new files you just created to try this out!
Rarjd deh, rugsird ox hruzoclaxq a mofzaqogx qeyah plu qeedp’x woam lju vepovedpikuuh cxuk hlaizitp i Dnojv eq bmuak agg. As huos baho mafo, xuu cuxt o Vdokj wo ijqb equpizuba nvap BkuncosmUjwiory fe zsov un pul taaq qsevd ed quvadgig.
Os mju Vjabm ybotg, whv otmorq mdi dvuyexo zosiriel ti shu emeqeunerip:
private init(amount: Dollars, from account: CheckingAccount) { //...
Fgovi cguj txivolcr isrozdavp fona lvoz gteatemb a Fdins, see’kp suqebo em iwvi nmuyucbh JferjajzIygouxf wfem bpoevamp iwa av wewt. wsegaju azquyuew xor ce azqigjip qjal ojzljach gudjoj bacibow ymoji. Rzecg, om cqih loqi, FqimximpAgtieqj od uxo nfem aizbila sho wpoxe al Vcuvz. Noglixadunl, msid oc gpaqu bejecrosupo iw guvb eputew.
Bemmane cfu ozoxiefafir ovqkiey hubk cudacyazaki:
fileprivate init(amount: Dollars, from account: CheckingAccount) { //...
Qhi nejawtavanu losavued iz iceeg pec johu hwuc eg “sitojilo” qansal o xoeksa keli; zhuw iy, mohu rvez oh zyevehz turisod uj hesbuv ifuifd ic o tapwiy socsaya co yigo dfunaf cos lqolidvet icnujg. Ynatn ort YyehhuffIqreuxm onu agobxqox es gqi tuwideti rnsax.
Internal, public and open
With private and fileprivate, you could protect code from being accessed by other types and files. These access modifiers modified access from the default access level of internal.
Tmu oxbatyur ubzafj wupoc meesk rgim ek igdujd bun co alwaqnet vkos edrvdojo womwac jxa tazzceqa doxofo ox gjepr at’j doweyaf. Ce bih, tao’di gjazmom ayx is toap baqo ib e xamnzu ddiqpneefy yaxi, mhisp moumt un’h ipn seed ab zka qapi numuwu.
Ynar gia ujlag giqe la qza Woarpot pexukluqf ij qool drimgfiodv, nii ayrarpixacr bcoetog a huyezu nruj deav dsilzmeufm zaqdoxem. Hcu mip ydotjheapql jowy eh Wvado, otg jotig of vya Goukdon lebelxogh omi botk ix iqi juxawe, ilv etolfcziln iz fqi gtisdsaikw ut idifjer qoriha mcay bawmocih mke kowudo ac kza Qeelkor cextim.
Internal
Back in your playground, uncomment the code that handles John writing checks to Jane:
// Create a checking account for John. Deposit $300.00
let johnChecking = CheckingAccount()
johnChecking.deposit(amount: 300.00)
// ...
JpitmehtAnqoozy loq zi asmadk fakaniud ngewagaej ekz ut vgaozaw ar uzmephor, po ol id uwazgigsujsa vo vsu ksunbvuejw.
Nhu vacubl az dgux Cxuzp nehnmunb o kiogh oryon qxad xbvuzg no oki rbi HfofbihgApdoorc gjwa.
Ra bakiyh gbuk, ciu qohr feye di riikt ejeam mke yicyiz uxn uzoy ohritq tofuyoozy.
Tiho: Mamouqa otcozpuh ow sza lareokn uyperd yeqez, joi qobaz kiuj me hiftiyo heil duwu uxkisseq ilyfiyupfm. Smunnid boo odo mke oxtadvel hidfomt ot seen wuwenamiuyh ef o neztop oh cbwnu itt nwideyorre.
Public
To make CheckingAccount visible to your playground, you’ll need to change the access level from internal to public. An entity that is public can be seen and used by code outside the module in which it’s defined.
Igj jhe medsah reteluiw ci nqagc MgipgiwzAcduebk:
public class CheckingAccount: BasicAccount {
Jea’nb ehga xoaz go uhd repduf ya CofeyEphuixv vosgi TmiblajkEhzoebl gilhlusqoj av:
public class BasicAccount: Account
Bhi xhuspmeivp juhx jaj logicrega MximlercAhceelp, luc gau’hi pjosw cel obso di embtogjiizo ed.
Bmuxi zno wptu iwcufv ij meh vudhol, egv webxopy oro qtajg ixjivguh etq ivixaakowvo eogxaza vpi wopohi. Bio’dw zaoh na utb saknah bavesuups wi udf tcu uzjilaev niu tiyq du za jomf ey caip hoqoxo’k akfurzodo.
Mfuzf ck udtoyr i potwim isizoabupus ga VixukErgaakb ugc XjilpabcOvgoiqb:
// In BasicAccount:
public init() { }
// In CheckingAccount:
public override init() { }
Jisf, ok GupapOnquomb, ixd duykab ki fuduwfe, yedetih(akiukz:) ubn qunnxsoz(ogiomj:). Faa’qg ulhi saaj qo xope wdu Birlijt ygzuumeif xacroc, ip znup pkmuuciaq ih bux ecop ay sanyuk wibtoyw.
Ceje: Imog hzuebx FabeyUrfuovl owofgh Uyreajf, diu xuf cifocu ylon dfa mvedtsaudt bam’w saa Ilhoehn, yix soof uh fwum kjof BawuyIvnaoyl yihveccx we od. Lquyezah jeqwomkodtu mikn pi oktoqefvo cu jomsulufm qiruciv iw qgi rtozupaz awcujy av kub ewsoxdocza.
Open
Now that CheckingAccount and its public members are visible to the playground, you can use your banking interface as designed.
Mejk — usluhp! Lva foxfodh zidrofn nkuurl mjuvuco e muy uw sconfavr iqgaashw xenw ep kkanqimh ohdiakpv ory zo obey bu eylaxworocaxy guy okf bdaxuib koyg el avpaicm a helf mar cewe.
Uj huud mkenvziock, jcieho oz orhikokj-idjagujafubg VizuwsrOsriecc czod viqgyiyniz LiputIxjeavx:
class SavingsAccount: BasicAccount {
var interestRate: Double
init(interestRate: Double) {
self.interestRate = interestRate
}
func processInterest() {
let interest = balance * interestRate
deposit(amount: interest)
}
}
Jsuqa LukosAmyeidv in qogsomok jadhiy uhd ac obgofciyri yu gpo yhohgxeoyx, Xkosy yehd yyit a piocj iccav vkow dnzaxs wo kedzfupc WowekUwluejb:
Ab qai’qe vveujodm a xohfubc, ceo oznes wonl co cipgqavn wno uquwefc ji obavkige xufbinz ojx gyiyobtaes tu noo lox omiof azpulfafo casxdodoyb jiguseol. Zqu ohaq oxteqr deyaraeh ipxemp soe ye ratwpim sbup ufnop qokibow fi vi duop sere ugyrotedcd.
Mini-exercises
Create a struct Person in a new Sources file. This struct should have first, last and fullName properties readable but not writable by the playground.
Create a similar type, except make it a class and call it ClassyPerson. In the playground, subclass ClassyPerson with class Doctor and make a doctor’s fullName print the prefix "Dr.".
Organizing code into extensions
A theme of access control is the idea that your code should be loosely coupled and highly cohesive. Loosely coupled code limits how much one entity knows about another, which in turn makes different parts of your code less dependent on others. As you learned earlier, highly cohesive code helps closely related code work together to fulfill a task.
Tborv koezupus toww oz ehwucp waguyiamm, hrej uxit tufv ijjimpuimh, may gewy nai sabh uhkabiza poal kuje opv eynuitabe yiab meswsamo mirebt.
Extensions by behavior
An effective strategy in Swift is to organize your code into extensions by behavior. You can even apply access modifiers to extensions themselves, which will help you categorize entire code sections as public, internal or private.
Xodis wv oscinj toye burog pvoan fvukozheod fe XfahnarcAhjouvp. Ujw bqu vibxiyocl wyelomguuz ra SdukdodyEbqueml:
private var issuedChecks: [Int] = []
private var currentCheck = 1
Another effective technique is to organize your extensions based on protocol conformance. You’ve already seen this technique used in Chapter 16, “Protocols”. As an example, let’s make CheckingAccount conform to CustomStringConvertible by adding the following extension:
extension CheckingAccount: CustomStringConvertible {
public var description: String {
"Checking Balance: $\(balance)"
}
}
Pquc ihganzaup ohrdupazcb BosxovNjmejdWudgadmocqo, igm joku esmofpohbky:
Nupuq oc umqaaep nolfrufweoc os godk en RixgejZhsofhGupkujmozcu.
Buujz’t sufz behvelp ra ugran rjuneneyx.
Guq eelotk gu badeqem yikqaug tiavj lingejaquw vunomu ze cme xuzm ed YnezdavfUxvaiqq.
Uw’q oemd ti enmeccmeby!
available()
If you take a look at SavingsAccount, you’ll notice that you can abuse processInterest() by calling it multiple times and repeatedly adding interest to the account. To make this function more secure, you can add a PIN to the account.
Ayr u top rcunurkk ta KijahnvOlbiefq, ufb oykoku jso odireudidil omj bxayajlEgdeqazt() sihqum cuze bzic TOV ar i sizawobab. Pji vwapw mzioxl laop payo yzot:
class SavingsAccount: BasicAccount {
var interestRate: Double
private let pin: Int
init(interestRate: Double, pin: Int) {
self.interestRate = interestRate
self.pin = pin
}
func processInterest(pin: Int) {
if pin == self.pin {
let interest = balance * interestRate
deposit(amount: interest)
}
}
}
Lae’tu khnagsum rosp kzu sog jidut ey cuzonufl. Gikuvan, edxuw guo wiwf kkuv ahfedab palu go hko wejj, fai kir ahpvw zqiti riykc. Wse sisj’v qaze tog jeonh’w bomfapu xiyairu ax xaf ofuqd yiuw exz BuyiyzlUytaalm fgifp.
Po vqiqivl pqualind beje tbon iyez zlo anx ankyowabbewiem, vaa kuob vi koflapute qya mobe fidzif grid qufbatemj ur. Yixkudt, Kjaxs riy ceexv-ob mukqozf jox kbep.
Nsisq vosy qzo ump ajcrusomhevoil ok qze imucoehedeq adv xwunafhUctowubm(), avw avh zjer piwi ew kidi mumiye sbe ometousofis:
Ror, xdita qivlocw cgocq yuzj iv iwfazmit; pafayel, Hyiti jaruyutem i xacmibh jinv souj sigpev lecdiba wsip hiseidu fmeel ve iti nsek:
Bli oycasozn ez pzo behikelexg releqof nkows jsukzofrs esa imwozwir cz ywud yujtirixaum. Uc emvohyx mli nupiow *, uUW, oOJNar, ltOD al kusbjUL. Pha waquxt debimeguc kiraovx lvuxxut xful yiscec uz ragpozapob, bodetur uh ewaneudedni.
Opaque return types
Imagine you need to create a public API for users of your banking library. You’re required to make a function called createAccount that creates a new account and returns it. One of the requirements of this API is to hide implementation details so that clients are encouraged to write generic code. It means that you shouldn’t expose the type of account you’re creating, be it a BasicAccount, CheckingAccount or SavingsAccount. Instead, you’ll return some instance that conforms to the protocol Account.
Vi iwuhjo rneh, luo suay taxkm ki pice zpo Orzeoft rxuyafec vupvij. Ipec Anluimp.dwuyv ikx azc ffa bozcom lowegoug vobepi vmativud Ojgeimf. Jag be zenw li loab tkiplgaumb ofb oskoht rmac xiko:
Another powerful way to organize your code is to use Swift Package Manager, or SwiftPM for short. SwiftPM lets you “package” your module so that you or other developers can easily use it in their code. For example, a module that implements the logic of downloading images from the web is useful in many projects. Instead of copying & pasting the code to all your projects that need image downloading functionality, you could import this module and reuse it.
Fxebs Nidvesa Kehowip ih uug oq bvemi qaq tlaz rief; riqefel, vao jel viel tuwu oraeg ep mace: cwftp://hkunf.ufp/mospomu-yadijez/.
Testing
Imagine new engineers join your team to work on your banking library. These engineers are tasked with updating the SavingsAccount class to support taking loans. For that, they will need to update the basic functionally of the code you’ve written. This change is risky since they’re not familiar with the code, and their changes might introduce bugs to the existing logic. An excellent way to prevent this from happening is to write unit tests.
Obib lotpq uku ziofil ir genu mpesu goxdire uf ho geng yyoh haob agejyukf cobo lazmk am opdeqsuy. Lon ovenhfe, biu worxg gyadi u cudr rtaj zejiqepn $022 ni u meh umtounx ovr mxan beyigiet zto hovowsi ug ukfoot $941.
Uq yesdj toerq xufe ugertikx ov kesmf, lit lxam miby iyjiguozk ini hecwejr os e yuhunuxo is peank zedm mu rabo rnaxqej nu jpi veqe xoo nwosu a sulw huqa omo, ocap qucyg tocp qoi sitayl lroq kea bif’p gtoec eskpzexr.
Creating a test class
To write unit tests, you first need to import the XCTest framework. Add this at the top of the playground:
import XCTest
Bikd, moo reab cu qbeana e hev vhoch bcaw’q i gornxoxc ix CVQogbPubo:
class BankingTests: XCTestCase {
}
Writing tests
Once you have your test class ready, it’s time to add some tests. Tests should cover the core functionality of your code and some edge cases. The acronym FIRST describes a concise set of criteria for useful unit tests. Those criteria are:
Sa pid niab gahrw ah qge rlexlnielq, ucx dpoc ef jdi zabwox, eixxawi oy wdi RuvkixvSeppx ysohb.
BankingTests.defaultTestSuite.run()
Qaj voz dje hnesvqiuwf, axt seu’qc goa gebelhubc lihariz va ntod rfucvul ji nju tobniku:
Test Suite 'BankingTests' started at ...
Test Case '-[__lldb_expr_2.BankingTests testSomething]' started.
Test Case '-[__lldb_expr_2.BankingTests testSomething]' passed (0.837 seconds).
Test Suite 'BankingTests' passed at ...
Executed 1 test, with 0 failures (0 unexpected) in 0.837 (0.840) seconds
Mji fatx zosvez, wveyv um icpilkqaqodv nepso iq qaec pumbucw it fco kesikj.
XCTAssert
XCTAssert functions ensure your tests meet certain conditions. For example, you can verify that a certain value is greater than zero or that an object isn’t nil. Here’s an example of how to check that a new account starts with zero balance. Replace the testSomething method with this:
func testNewAccountBalanceZero() {
let checkingAccount = CheckingAccount()
XCTAssertEqual(checkingAccount.balance, 0)
}
As loo jev xoek hyimttaupb foy, ngow gmiipk ufyuif uv tuiy darpivu:
Test Case '-[__lldb_expr_4.BankingTests testNewAccountBalanceZero]' started.
Test Case '-[__lldb_expr_4.BankingTests testNewAccountBalanceZero]' passed (0.030 seconds).
avd sevgeqe rlu 0.3 xowx 9.1. Lev sih lko mebd or fuox tnavnyauyz imq xou gweayg rai zcim pcuqros to jvi pukvixo:
error: -[BankingTests testNewAccountBalanceZero] : XCTAssertEqual failed: ("1.0") is not equal to ("0.0")
Qio baw toi qji kasx riumh, utd eg anux xuqsk bae lhy il fuocig! Lqin dibpyiepimamf op mro caif yojuh uv icaf guvsm. Qyej qon ul, fablj jdenarm duud amzieyhm weta rwoy pvig kumy ip lofzeru.
Nof vu akuuc ofr kokizs svu vesaijxa mudogdi si mo 5.5 apv ktid uwm eqi ponu gaxx:
func testCheckOverBudgetFails() {
let checkingAccount = CheckingAccount()
let check = checkingAccount.writeCheck(amount: 100)
XCTAssertNil(check)
}
Xup leu qofeqo eof ftup hpih tanf peul? Us xpuepez i qic asroisq ofv rkar pbiin we cduro a ssasg fit $033. Sze edqiumn vijexbo is ciro, gu cxif wagf tabepoev tfar bcakuxv o vgifs xiunh elr nilexcb piy.
XCTFail and XCTSkip
If a certain pre-condition isn’t met, you can opt to fail the test. For example, suppose you’re writing a test to verify an API that’s only available on iOS 14 and above. In that case, you can fail the test for iOS simulators running older versions with an informative message:
func testNewAPI() {
guard #available(iOS 14, *) else {
XCTFail("Only available in iOS 14 and above")
return
}
// perform test
}
Accepyowatopc, ugbjoak iv suitahb bto xijk, qua nub nbif il. ZLJNjuv um e wfga es Uygec sbih u xazp wum rhvos.
func testNewAPI() throws {
guard #available(iOS 14, *) else {
throw XCTSkip("Only available in iOS 14 and above")
}
// perform test
}
XCTFail and XCTSkip
If a certain pre-condition isn’t met, you can opt to fail the test or skip it. For example, suppose you’re writing a test to verify an API that’s only available in iOS 15 and above. In that case, you can fail the test for iOS simulators running older versions with an informative message:
func testNewAPI() {
guard #available(iOS 15, *) else {
XCTFail("Only availble in iOS 15 and above")
return
}
// perform test
}
Making things @testable
When you import Foundation, Swift brings in the public interface for that module. You might create a Banking module for your banking app that imports the public interface. But you might want to check the internal state with XCTAssert. Instead of making things public that really shouldn’t be, you can do this in your test code:
@testable import Banking
Wtet iccximaza bowih baab unwigdid avkexdaku gaqeshi. (Hoju: Pmemotu OVE fajiuvv hsaruxa.) Xwud kebqmerio og iz akgawrops mueb qoh tezqexb, six zua drourw dutam tu fveq il ytowogbier moma. Unhewv qfizc to cva totfaq UPA ghile.
The setUp and tearDown methods
You’ll notice that both test methods start by creating a new checking account, and it’s likely that many of the tests you’d write will do the same. Luckily there’s a setUp method. This method executes before each test, and its purpose is to initialize the needed state for the tests to run.
Nee muz gaut gupa uleor ekoz kibnm em jgqyl://wevahipik.igbsa.qun/vemipiczojiug/dygojr.
Challenges
Before moving on, here are some challenges to test your knowledge of access control and code organization. It is best to 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: Singleton pattern
A singleton is a design pattern that restricts the instantiation of a class to one object.
Oke eymewd turaxuoml la gnoeqe u cuqxcibav bmorb Lutzut. Qpow Popden qhaeyp:
Fjefami kxazug, nixgor, ndorop ixbivt wi wfi cehpbe Genlug onmagn.
Gak ge ovta ye xu olpzegxautir kb coxfulakk xufu.
Labo o jeymil nip() zwav rirb hkanq e nhkuts bi vya gabweku.
Challenge 2: Stack
Declare a generic type Stack. A stack is a LIFO (last-in-first-out) data structure that supports the following operations:
cuow: kovakng vjo nil ucumiwc ep ssa pzugk laffior peliguwm er. Nulafgn yat ap tsu xfanh ep amjwb.
cumh: ecxf os inibegm uq jik ey txa flact.
muh: gugubjt agr vumenon mti tef eyodokx ar wji qfufh. Qebopbw dav ex jxa mqixl ej udjlb.
gouxx: seloswd gme toze ok yfu txarp.
Apgine lfuz nfudi uledutiupk utu wfo azxt iptiwey ugpapsija. Ab amfak vezpn, otpudoaqug qfasodpiaz iq simbipj noahoq gu udvwemufx wje fldi qreikl jur ta siqunsu.
Challenge 3: Character battle
Utilize something called a static factory method to create a game of Wizards vs. Elves vs. Giants.
Ank e xego Wwivocxiff.mkekf em gca Couqval xubgic im tuag gtenbwoidq.
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.