How I Refactored a (Massive) MVC iOS App? (Part 3)

Tadeh Alexani
Formaloo
Published in
4 min readMay 10, 2020

Don’t forget to read part 2 before moving to this article.

So let’s continue our job with refactoring our network layer and the app routing flow:

Refactoring Network Layer

I’ve written an HTTP Client class that handles global requests to our API (GET, POST & PATCH) and a class for each type (e.g authentication and profile and etc.) which uses that global classes to send a request to a specific URL and returns wether a response JSON or an Error object. The thing I did is wrong in my opinion is that I then parsed the JSON and made a model object in my view controller which causes the increase in the size of my view controllers. So simply I used the builder pattern explained in part 2 and moved that code to my network layer classes. Now, I just return a model object or an array of model objects to my view controller and caught the parsing part from it which causes a huge decrease in the number of lines of my view controller.

I also put the HTTPClient class which I wrote using Alamofire on Github’s Gist so you can use it in your code:

HTTPClient.swift

Refactoring App Routing

I really like how segue works on iOS and Swift, but the thing that hurts me is the amount of code I used to call prepareSegue function and also performing segues, besides that the number of static variables to represent the segue identifier. So after a little searching, I came across the Coordinator pattern which is firstly is introduced by Soroush Khanlou. What is this pattern and how it’s working? To explain it briefly I can quote from Paul Hudson, author of HackingWithSwift.com:

To solve this problem cleanly, the coordinator pattern lets us decouple our view controllers so that each one has no idea what view controller came before or comes next — or even that a chain of view controllers exists.

Instead, your app flow is controlled using a coordinator, and your view communicates only with a coordinator. If you want to have users authenticate themselves, ask the coordinator to show an authentication dialog — it can take care of figuring out what that means and presenting it appropriately.

The result is that you’ll find you can use your view controllers in any order, using them and reusing them as needed — there’s no longer a hard-coded path through your app. It doesn’t matter if five different parts of your app might trigger user authentication because they can all call the same method on their coordinator.

So, how I implemented this pattern on my own app?

As I mentioned in the first part of this series of articles, I refactored my app UI by separating the Main.storyboard view controllers and add some storyboard references to connect them to each other.

But using the coordinator pattern you can commit the segues entirely.

I recommend you to watch Pauls Hudson’s tutorial on the Coordinator pattern because I find it really helpful:

This tutorial is written for a single storyboard, so you can make it dynamic using the code below:

// https://stackoverflow.com/a/54782950enum OurStoryboards: String{
case MainPage = "MainPage"
case Catalog = "Catalog"
case Search = "Search"
case Info = "Info"
case Cart = "Cart"
}

protocol Storyboarded {
static func instantiate(_ storyboardId: OurStoryboards) -> Self
}

extension Storyboarded where Self: UIViewController {
static func instantiate(_ storyboardId: OurStoryboards) -> Self {

let id = String(describing: self)
// load our storyboard
var storyboard = UIStoryboard()
switch storyboardId {
case .MainPage:
storyboard = UIStoryboard(name: OurStoryboards.MainPage.rawValue ,bundle: Bundle.main)
case .Catalog:
storyboard = UIStoryboard(name: OurStoryboards.Catalog.rawValue ,bundle: Bundle.main)
case .Search:
storyboard = UIStoryboard(name: OurStoryboards.Search.rawValue ,bundle: Bundle.main)
case .Info:
storyboard = UIStoryboard(name: OurStoryboards.Info.rawValue ,bundle: Bundle.main)
case .Cart:
storyboard = UIStoryboard(name: OurStoryboards.Cart.rawValue ,bundle: Bundle.main)
}
// instantiate a view controller with that identifier, and force cast as the type that was requested
return storyboard.instantiateViewController(withIdentifier: id) as! Self
}

}

Also, if you got a problem with launching your initial view controller in the App Delegate file (and see no result) I suggest you move it to the new Scene Delegate added in iOS 13 and Xcode 11 to make it work:

What’s the next step?

In the next and final part of our refactoring article series, we’ll discuss how to separate table view data sources & delegates and have a conclusion on all 4 parts. See you on the 4th and final part of my article!✌️

--

--