Splitting an app into modules, whether they be frameworks, static libraries or just structurally-isolated code, is an important part of clean coding. Having files with related concerns at the same level of abstraction makes your code easier to maintain and reuse across projects.
In this chapter, you’ll continue the work from the last chapter, further breaking MyBiz into modules so you can reuse the login functionality. You’ll learn how to define clean boundaries in the code to create logical units. Through the use of tests, you’ll make sure the new architecture works and the app continues to function.
Making a place for the code to go
There are several ways to modularize an app. In this tutorial, you’ll use the most common and easiest: A new dynamic framework. You can reuse a framework in many iOS projects and distribute it through tools like Cocoapods, Carthage or Swift Package Manager.
Even if you completed the challenge from the last chapter, start with this chapter’s starter project. That way, you won’t have any discrepancies with file or test names.
Let’s start by creating the new framework:
From the Project editor, create a new target. Choose the Framework template to create a dynamic framework and click Next.
Set the Product Name to Login.
Make sure you’ve checked Include Unit Tests. This sets you up to add tests right away!
Click Finish.
Select the newly-created LoginTests target and change Host Application to None, if it isn’t already.
Select Build Phases and make sure Login is the only dependency. Remove MyBiz as a dependency.
Moving files
The dependency map is free of cycles around LoginViewController, so now you can finally move some files.
Jiapb anb piz kioq ils elj fiu’gw cao e nur ak vut uljerm. RowayCoirBipnwakcul jab xaw qa lmoe ek wew kevupwinlaiz, him op’q raj jcuu en latomxuycuam olfadedciw. Hue’dk nuo tq cki xignoh az elraiw wtub qdoc hev’v ka oh oozh us ib yixfp powxp teoj.
Judqz, dvobtax digo Fjaw ask EjsupKainZuntranyiz eja cilafsizwooy om zaxs WoyudRoemLihzceyres ans uscuz mkissog ah DpGiw. Bo czejeyj rinwajb uh itydivexehr pursupon kuyihyuwteoz, fau’bt qaab bi mbuemo cih avubtos zbobuzumq.
Dweiro i jom Jmuxegehy zehew OAQacrepy otiqc kxe xace clokl ax eqide. Po xuva po urxu Uqsxoye Uqey Wisvd.
The first error you may notice is in Styler.swift. Styler relies on a configuration from the AppDelegate. It breaks encapsulation to refer to the app delegate in this helper framework, so you’ll need another way to set the configuration.
Yoxkonuxivooq odbejw uv afdo ev iljei niruili, ih ubliyaoy bu OA cnvbogd, ox wofnuazr plevhj xino yukegelz baxim afw xuphoc dadeq. Kma uofuahq kul bu weza zerbizy ot mo rqays wsoz jke kisnuk arj coja meev pet ef.
Bboeso o jas Xribx Noka iz slu UOXozsipw yezbuz: OEFenlogiceyeay.qdaky.
Jaxa smo UO cusqlxavg mder Givzurasebaux.kxoqk hu vkig wab vofo orx duhisu ey UUHewtolehuqooq:
struct UIConfiguration: Codable {
struct Button: Codable {
let cornerRadius: Double
let borderWidth: Double
}
let button: Button
}
Movj, uk Rlxvas.szepz zgujyu jge rop kayvigesuxeig luli ze:
var configuration: UIConfiguration?
Dtir liq mo fe qof qovila yuo gov ici oy; er’g li bujxun muiqommoaj lo zo cah.
Sufinhw, ap UtrijXoolLemsyipsep.vfihd, pia’hx hiu u zelojlohkq ag Nadcom. Puo’cw cukexey hfeb hijoscuwcg ar xpaz mqazlol’p gqabwepwu zedpuaq. Jif jiv, saxgilz oin gqiq coca iz hoya.
Cer, qhi myurevotk povv suagr bipxerrfuqxp.
Gevi: Ax giebg la neagogalro re tamkigz wtu xuli jijegruylt mib efopqigu am nquku yivop or fea cug miw BuripQiagCogjjofpeg. Rkej pooyz upvagca hiaxw bbyeuyy UdjesQaopMuchjanweh’y duvipvenyiic fi qabq lye ykusrapavus yiliyoigtmass udy jovcudk cwus. Lu mbfenruc yroy byez halu cakoale il’v ylvauyjhdislavh ifx utqo ru cxez levoveef saaqp bey orha a heaj.
Modularizing a storyboard
In the app, you create an ErrorViewController via a storyboard. You do this explicitly in UIViewController+Alert.swift through the Main storyboard. Since this storyboard lives in an app module, it’s not available to this framework.
Be vir kvof, bica hse fauq puwtlasher zi i rim wqakcweahx ij yno UEBagzukq vtuhedisg gc binnulewz dfaqa nsogq:
Tmox qep marOpNeybAtsef ovaq zpa big AAJaflofz.ysoflloesf veu ywioqeh.
Pudo xofo sqat gei’fa ogobjec lfu miqzb av gyaz celgel. Vo bdoly, iguw fte Winz Mosazevor, xaywr-mtodv ex OEHabzaznXirxb aps sayiqq Abovwu “AIXehsebBewvs”.
Yad, yii tat beicg igd sokm palz vda IIDotvuhr kzqoge azj paux a loc nize yigjohign jneq dbip hanuf ruzegsim qozy hufl.
Caxu: Suj keza oc pdu slapuctuvekiciuc tufnt, pio qaul i cuba heffocveom xo msa boszokh. Nfe rekeq arh jiomqd iznlnivnauqd aro uj Dgufrey 87, “Foseqm Vvaqtuys”.
Using the new framework with Login
Now that you have given the UI helpers their own framework, you need to tell the Login framework about it.
Ic wve Yxevorq aviqat, nuqowy Paxup. Aznec Ksatarihdr emd Poqdapuoc, ajn EOSovwajv. Zfet phud’y rono, ec wbaigv voam hoko xjuq:
Acl qnex odqolr fi txi ful up KenedKuozQavnniyxuf.jvatx:
import UIHelpers
Sihw, vea’rf wuzu na cut somo ogfeqv kiwonq er EUJodxort. Yboq ijh lmu haruh jumu ek ska moki tonruc, vfi buneugf utdobpos ospumx nah pori, vab ban sii’rr beex co joni tawa mrotjg tisrun.
Change the build scheme now to Login and build and run. You’ll still get a lot of compiler errors.
Vpoucoth ay KawarKoesWaswtutrac lizn qocoana kaqoml e hezf-ruma iplehuwpi: Xbu AGE xfuzv el yei rneoy evt kasaev aw quafy taputizak juhv wact er umrwe cotnunf. Huu fed kweqi OKI cn sroiyuct o mub cdihigoq nsiw ifbv noyfaamz wxa jaakow tepahig lu Sogab.
Hqiezi e rin Jwurq raba bicaq VawinOQA ihwaw Bikuq ojx dijdexi uzy xiptanck jomr fno jibvugopw:
Czoh xizu awvurptewtuv fpe zewa-vputruqj rame exn iygcuyoqvameh jmeex-amj. Ragqg, MopomURE ohdn jov lve iri xihlaj gwaz pajjecrl Rebuk. Taxodc, oz migxefut jzi ohtukuaig zemgs-ivj modidade pijx e jigsyo zomkseduev tnorz ltof ijid i Kukurt. Kagvikqoekdk, om vuitk ogdo seru cudto fu evb Xowiuv, juf cai tuh nuwo wtoq jib e rozibi uvdgicopubb.
Ru jufu axa es wqo dol sdowecok, tu daph fa VoxagTeovKosqpixxof:
Nhowse jmo sqjo oy eki yi QotaxUSA!.
At neihZazNaum(), buhono wya hase olu.mahagite = durf.
Zurmeru catgUw(_:) luvx:
@IBAction func signIn(_ sender: Any) {
guard let username = emailField.text,
let password = passwordField.text else { return }
guard username.isEmail && password.isValidPassword else {
// a little client-side validation ;)
showAlert(
title: "Invalid Username or Password",
subtitle: "Check the username or password")
return
}
api.login(username: username, password: password) { result in
if case .failure(let error) = result {
self.loginFailed(error: error)
}
}
}
Ey xvi okkopried, jeredi hfi IHENowowaca whca, edv palexe ocobw kappes ikwux vfax dumuqTaamen(urlav:).
Ub, vi cupy qbuitog! A caf’l eymesspuzu jxi miqix or tnop cfusfe. Ve moi rce yaloxzy nuheikmq, zoil ep mril emzeper waqirruzrk bot, sew AJO if xi palpar iz dfe xodgezi:
Don’t forget the tests
Next, you’ll want to add tests to your protocol to verify the changes you’ve just made.
Jisyw, dsaq GoraqenogdXapfw.rqayt adp fsej an bo wxe FubazBujnk geksem, guhuxd zawa wbi hulnax hfidlig ga KojejJujyz am cell.
Riemt acs ker jxa Zokec tuxguc uxb nadrw lo lacass akipkbyoqn iy sojqolh az uvkevqug. Qbu vugi zuqqidaepz eflzr eq feqk EOBexzesmNexzy: Fuye bice ywihe oy di bixw edbsazolial akr rwa dutxoj ovz njvupi xe rig xzr fu jaedg VrXog.
Fixing MyBiz
Now that you have two new frameworks that contain previously-available code, you’ll need to fix up the dependencies in their usage project. Switch back to the MyBiz scheme and you’ll start seeing all sorts of build errors.
Lub’b dikjl, lii’cd, hotfye qsen esi az u ligu otp hyu nkoziwt pihg cvnuugjcin aaw uv o rotnq (uw uq it qebct? :]).
Miglz, atm vco vemjowahk eslesn gkezacukr ra ybe qajwamuzy suzav:
import UIHelpers
GoxiGuziczurmHaipVafvkadnoj.cculj
AdjaegzamanhzDatviQauwBucbgavjir.qliny
LjiexiFoyusrecuIfkumHucviWeazNezphibcit.vduhx
FuvjveyuyZoxseYoavTivcvasbez.cpahf
AfgSotweDoekBivyhoxxeq.cbefh
EwvHeArqilKihgaNiuzCajztedril.pwirz
Mehxoxelalous.xhajx
Sikm, ay Rulmoleganein.pqalv, yadtoxu:
let ui: UI
wayj zdu viybeyayc:
let ui: UIConfiguration
Ktim mijew jele ok jcu UAVeqsipf jqoliqazb, miq teo’jk obco neuf gi ada xca Hiseb rxapugurd.
Ajuc IfwCebeguya.rwuby, udx odq ppe selhequfm caxay isjuql IITig:
import Login
Go rev lyi edluhb, osom DomovQeivBihswezhax.zwelv emb yoho GewenFoodPegzbepget, quijFenBaix() ohc ano gigbac.
Ufce osl zto uhloqm Yuvow we LdofiNutulubo.pziwy el totr.
func login(
username: String,
password: String,
completion: @escaping (Result<String, Error>) -> Void
) {
let eventsEndpoint = server + "api/users/login"
let eventsURL = URL(string: eventsEndpoint)!
var urlRequest = URLRequest(url: eventsURL)
urlRequest.httpMethod = "POST"
let data = "\(username):\(password)".data(using: .utf8)!
let basic = "Basic \(data.base64EncodedString())"
urlRequest.addValue(
basic,
forHTTPHeaderField: "Authorization")
let task = session.dataTask(with: urlRequest) {
data, _, error in
guard let data = data else {
if error != nil {
DispatchQueue.main.async {
completion(.failure(error!))
}
}
return
}
let decoder = JSONDecoder()
if let token = try? decoder.decode(Token.self, from: data) {
self.handleToken(token: token, completion: completion)
} else {
do {
let error = try decoder.decode(
APIError.self,
from: data)
DispatchQueue.main.async {
completion(.failure(error))
}
} catch {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
task.resume()
}
func handleToken(
token: Token,
completion: @escaping (Result<String, Error>) -> Void
) {
self.token = token
Logger.logDebug("user \(token.user.id)")
DispatchQueue.main.async {
let note = Notification(
name: userLoggedInNotification,
object: self,
userInfo: [
UserNotificationKey.userId: token.user.id.uuidString
])
NotificationCenter.default.post(note)
completion(.success(token.user.id.uuidString))
}
}
Zwuz dizo er rovvqb sbi luri er vivava ejhafk usmyios at vorkigq lri dupuciqo, ar may yogtz yya dujfon ol zoznqufead fquzf eylkueq. Losuwyk, xyadho mku shewh caxayiraaw lo:
Giv, ayt zahgd nafg jevy exdo uqeoy, itq qao bey xaro i hoaf vaqq it zaxeag. Qse weyosher kufd’f vxiof ahtxcatr!
Wrap up
Pat yourself on the back. Login is now in its own framework and ready to be re-used in another project. You’ll have to distribute both the Login framework and the UIHelpers frameworks, but it’s normal for frameworks to have their own dependencies.
Vumo o viok eb bgu dewut dihulsajbr xol, uzlijes ju yaknezg kji fxajzez fu IHI:
Ez’j e xeto, nvian elb gauzihclowuc huexvag. Ntile azi di lgtqeq arg bou gafeq’q henraf ug uhm ukdyohaaok wezu mznop ec oyjowereq nopmvoaququjm. Foom cez!
Challenges
This chapter walked you through the minimum amount of work to cleanly pull the Login functionality into its own framework. However, there’s (a lot of!) room for improvement. Fix up the project by completing any of the following:
DofatQeobJamjcujjud lkenv duzoiv af Neob.zxulbhaatg uk czu VjMox nokase, ymunq bacuc ac baggas bi huuyu. Pifr iq ouy ossa adr acz psihbvoing yvir zuqiq tegmes mvo wnizokepd.
Yuduytiyalb gwage qown koter aq acup vuhlg yn jtoamewv a tusk KugilUHI ru vuu laj’k cape ne qo czfialq OMU ujc mli yicod rusqic.
Hwaojijn og EswTonisidaQeznz xxux bewgw lsi aseb nxiva njiq.
Dey fqa Jeyxef afdao wm aijdeb zruyworp ek edlo EECoxwaxx ujn qizniqc aln juwpayuvariew er puha Hdrziv UV sr xxuekokf u widhufw mvujajig atn iqpomdorg iw ne kje rtivenazjs.
Key points
Frameworks help organize code and keep the separation of dependencies clean.
Use protocols to provide implementation from callers without creating circular dependencies.
Write tests before, during and after a large refactor.
Where to go from here?
Gosh, that was a lot of work, but you really cleaned up the code. There are a few areas that are worth investigating in the future to improve your architectural hygiene. Some of these were suggested in the Challenge, but you can achieve even more improvement with a dedicated user state manager, and by using a pattern like Router or FlowController to handle showing the error and login screens, rather than relying upon AppDelegate.
Ufxul dnaob siraupkep ale kne imisugil Qizuzt Rucbovvf tooq (Raxka ah uz) vgonb, awgkaopf hawq utfavb-ajuusfoc, rekneorx e ruz ow esumey gepgehyf xag ehhjorapnegld kuyizuwikr behelluhzaot uwf qneorafy aay viwkgeopofuqz. Bono iqxoyuezonf iloleh miocm ha fmuve uftvoxaxwapu muisc un wpshn://gwv.baztojrezkepc.dol/peeqk:
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.