Avoiding Massive View Controllers by refactoring.

Hi, this is going to be a very simple tutorial that will show you some suggestions that will help you avoid Massive View Controllers in your projects by making some refactoring following the MVVM design pattern, the single responsibility principle, and protocol-oriented programming.

As people say “less is more” and iOS it’s not the exception. Let me start by telling you the story of Paul, he is a developer that really enjoys to code and watch movies, he is just starting a new app that will show available movies in cinemas, he is very proud of his app so far, he already created the network layer using URLSession (excuse Paul on this one for not using a networking library), he also created the model Layer using a value type and provided a failable initializer for his model just in case the data response from the API is nil.

At this moment his app just shows the list of movies provided for the https://api.themoviedb.org API,  and displays it in a tableView, nothing too complicated but is just the beginning of the project,  he is so proud of his work on the viewController that at this point with 70 lines of code he named it MinimalVC.

1a

But what if his boss decides to add new UI elements programmatically into the ViewController, or also some other implementations of delegate methods of the tableView, what about some animation, who knows how does this app will end right? he realizes that although he is been doing a good job so far, he can do it better by making some refactoring, but he doesn’t know where to start, let’s help him.

Start by downloading or cloning his app here and run it, you should see this…

Simulator Screen Shot Apr 27, 2017, 13.00.52

Let’s explore the files, as  you can see most of the work is already done and because this is a refactoring tutorial that makes sense, you can see inside the service directory, two structs in charge of  networking and parsing; a protocol in the protocols directory for extending UIImageView for caching images, a Movie model  in the Model directory and a programmatically layout custom cell in the View directory.

Let’s jump into the MinimalVC and start making some refactoring, if you been working with tableViews for a while I am sure that the code inside the cellForRowAtIndexPath caught your attention, at this point this method it’s not only responsible for cells allocations and reusability, it is also handling the representable format of the data model. Let’s use MVVM to create a ViewModel that will handle this instead. Start by creating a new empty file and call it MovieViewModel, let’s create a model that will handle the presentable representation of our data, copy and paste this in your empty file.

import Foundation

struct MovieViewModel {
    let title: String
    let posterPath: String
    let overview: String
    let releaseDate: String
    var voteCount: String?
    init(model: Movie) {
        
        self.title = model.title.uppercased()
        //This endpoint is provided in the API documentation
        self.posterPath = "https://image.tmdb.org/t/p/w342" + model.posterPath
        self.overview = model.overview
        self.releaseDate = "Release date:" + " " + model.releaseDate
        self.voteCount = model.voteCount != nil ? "Number Of Reviews \(model.voteCount!)" : "No reviews"
    }
}

What we did here? we extract the responsibility of formatting the data from cellForRowAtIndexpath and assign it to a new object, it takes a Movie object as a parameter on initialization and uses it to model its representation.

We need to make some adjustments to Paul’s code to make this work, let’s start by creating a new function in the MovieCell. Copy and paste…

    //MARK: - Cell customization
    func setUpWith(_ viewModel: MovieViewModel) {
        movieImageView.loadImageUsingCacheWithURLString(viewModel.posterPath, placeHolder: nil) {_ in}
        movieTitleLabel.text = viewModel.title
        dateLabel.text = viewModel.releaseDate
        descriptionLabel.text = viewModel.overview
        reviewsCountLabel.text = viewModel.voteCount
    }

This function takes a MovieViewModel as a parameter and uses it to display the data in the UI elements. Now it’s time to use this new implementation inside the cellForRowAtIndexPath method it will look like this…

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: MinimalVC.cellID, for: indexPath) as? MovieCell else {
            fatalError()
        }
        let movie = movies[indexPath.row]
        let viewModel = MovieViewModel(model: movie)
        cell.setUpWith(viewModel)
        return cell
    }

Here we not only clean up our code, now our implementation is more readable and easy to test, we use the movie from the movies array to instantiate a MovieViewModel and then pass it to the cell to display it, cool we are making good progress so far but what else do you think that we can do here?

What about this error handling in our method?

  guard let cell = tableView.dequeueReusableCell(withIdentifier: MinimalVC.cellID, for: indexPath) as? MovieCell else {
            fatalError()
        }

There is nothing wrong with this of course but let me share with you how to handle cell registration and reusing with protocol extensions and generics. Let’s put our protocol-oriented programming and generics knowledge in use right now…

Create a new file and call it Reusable, copy and paste

//1 protocol declaration
protocol Reusable {}
//2 protocol extension 
extension Reusable where Self: UITableViewCell  {
    static var reuseIdentifier: String {
        return String(describing: self)
    }
}
//3 conforming to protocol
extension UITableViewCell: Reusable {}
//4 using generics to make code reusbale
extension UITableView {
    func registerUITableViewCell>(_ :T.Type) where T: Reusable {
        register(T.self, forCellReuseIdentifier: T.reuseIdentifier)
    } 

    func dequeueReusableCellUITableViewCell>(forIndexPath indexPath: IndexPath) -> T where T: Reusable {
      guard let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
            fatalError("Could not deque cell")
        }
        return cell
    }
}

In section one we declare a protocol called reusable, in section 2 we are using a protocol extension with a static property that will be accessible only to UITableViewCell types.
In section 3 we are conforming UITableViewCell to Reusable, and in section 4 we create two generic functions the first one, will handle cell registration and the second one will handle cell reusability,  both functions are constrained to the UITableViewCell class that conforms to reusable, cool now how we use this?

First, now that we have a static variable that will return a reuseIdentifier of any UITableViewCell subclass let’s remove the hard coded one on MinimalVC, start by removing this two lines…

//remove this property
    static let cellID = "cellID"
//remove this from viewDidLoad 
   tableView.register(MovieCell.self, forCellReuseIdentifier: MinimalVC.cellID)

Add this one for cell registration on viewDidLoad.

   tableView.register(MovieCell.self)

Also, now you can remove the guard statement from cellForRowAtIndexPath and use our generic function like so.

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {        
        let cell = tableView.dequeueReusableCell(forIndexPath: indexPath) as MovieCell
        let movie = movies[indexPath.row]
        let viewModel = MovieViewModel(model: movie)
        cell.setUpWith(viewModel)
        return cell
    }

Now we don’t even need to downcast it as an optional because all the safe check are handled by the generic function. If you want to see more about this implementation check this awesome article.

At this point, we’ve been doing small changes in the ViewController and it’s looking cleaner but still feels like it is handling different responsibilities, by now it’s a tableview manager and a Datasource, and depending on future implementations it will potentially acquire new ones.

Apple provides a default implementation for UITableviewDataSource and developers (most of the times) just stick with that. It’s important to follow the single responsibility principle and we will, by creating a new class that will extract the dataSource responsibility from the ViewController, we are going to do a bit of refactoring here and we will do it step by step.

Step one, create an empty file called MovieDataSource, don’t forget to import UIKit, copy and paste…

class MovieDataSource: NSObject, UITableViewDataSource {
    private var movies: [Movie]
    init(movies: [Movie]) {
        self.movies = movies
        super.init()
    }
}

Your compiler is giving you an error and that’s fine it just telling you that this object doesn’t conform to the UITableViewDataSource yet, and we will fix that in a second, this object has a movies property and an initializer that takes as an argument an array of movie objects.

BTW, if you are asking yourself why am I using a class instead of a struct, is because only subclasses of NSObject can conform to the UITableViewDataSource protocol.

Ok, go to MinimalVC and this time cut the cellForRowAtIndexPath and the numberOfRowInSection methods and paste it in your new class like so…

class MovieDataSource: NSObject, UITableViewDataSource {
    private var movies: [Movie]
    init(movies: [Movie]) {
        self.movies = movies
        super.init()
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(forIndexPath: indexPath) as MovieCell
        let movie = movies[indexPath.row]
        let viewModel = MovieViewModel(model: movie)
        cell.setUpWith(viewModel)
        return cell
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return movies.count
    }
}

Step two, we need to refactor the networking response, go inside the Service directory and open the MovieService file, on line 23 replace this …

    typealias MoviesNowPlayingCompletionHandler = (Result<[Movie?]>) -> ()

for this…

    typealias MoviesNowPlayingCompletionHandler = (Result<MovieDataSource>) -> ()

Here we just changed the constrained response to a MovieDataSource.now go to line 46 and replace this…

Now go to line 46 and replace this…

    let movieArray = movieJSONFeedArray.map{Movie(json: $0)}
    completion(.Success(movieArray))

for this….

     let movieArray = movieJSONFeedArray.flatMap{Movie(json: $0)}
     let movieDataSource = MovieDataSource(movies: movieArray)
     completion(.Success(movieDataSource))

Let’s see this code for a second, as you can see now we are instantiating a MovieDataSource object with the movieArray and passing it as an argument for the completion block, but also we changed the map function for the flatMap, but why?

This is actually a very cool feature of this high order function, if you go to the Movie object model, you can see that because we provide a failable initializer we can get a nil object on creation, if we use the map function we will potentially get an array with nil objects on it and we will  have to check if each object  is not nil and move from there, but the flatMap will do the check for you and just return objects that were successfully initialized, pretty cool.

Step 3, let’s go to MinimalVC for one last time, we are going to replace the movies array for a movieDataSource optional property,  we will change the implementation of the MovieService networking call, and finally, we will create two private methods to clean up the code inside viewDidLoad, replace all the implementation in MinimalVC for this…

class MinimalVC: UITableViewController {
    var movieDataSource: MovieDataSource? {
        didSet { tableView.reloadData() }
    } 
    override func viewDidLoad() {
        super.viewDidLoad()
        setUpViews()
        performNetworkCall()
    } 
    private func setUpViews() {
        view.backgroundColor = .white
        tableView.register(MovieCell.self)
        tableView.estimatedRowHeight = 100
        tableView.rowHeight = UITableViewAutomaticDimension
        self.title = "Movies"
    }
    private func performNetworkCall() {
        MovieService.sharedInstance.getNowPlayingMovies { [weak self] (result) in
            guard let strongSelf = self else { return }
            switch result {
            case .Success(let dataSource):
                strongSelf.movieDataSource = dataSource
                strongSelf.tableView.dataSource = strongSelf.movieDataSource
            case .Error(let error):
                print(error)
            }
        }
    }
}

Because of the changes that we did on the MovieService now the networking call is returning on success a MovieDataSource object that we assign as value to the movieDataSource property and then assign it to the tableView Datasource.

So, why not just pass the result directly to the tableview Datasource? well, that’s because when you decide to go with this approach by separating the dataSource from the viewController you need always to assign the value of the Datasource to a property (that will hold a strong reference) first and then assign it to the tableview Datasource.

Now run the app, and you should see that it looks exactly the same but now we made a lot of refactoring making our code more testable, cleaner and most important separating responsibilities between objects, let’s hope that Paul is happier now with a viewController with just 40 lines of code.

I decide to write this because I didn’t find many tutorials about best practices in terms of refactoring, I am pretty sure that there is always room for improvement and if you have any thoughts it will be great if you can share them.

If you are asking ok this looks cool but what if I want to use the DidSelect delegate method in the ViewController how does I get the item from indexPath without having access to the private array of movies in MovieDataSource, well if you can’t figure it out and want to know how to, send me a tweet or just leave a comment and I will potentially start a second part for this tutorial.

Hope this help you in your future implementations!

You can find the complete project here.

Do you need help in your app development? visit StartAppStudio.com

Peace!

twitter

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s