My thoughts about MVC in an Apple world

On to one of the most discussed topics in the Apple developer community: How to leverage MVC, or “How to avoid the Massive ViewController”. What I think about this, and why I don’t really care about design patterns? Read on.

A history lesson

The MVC pattern was created in the 1970s by Smalltalk developers, so it’s old, but it still serves us pretty well. Smalltalk itself heavily influenced the creators of Objective-C at NEXTStep before Apple acquired NEXTStep and made Objective-C and MVC the standard for all Apple developers.

Enough history, on to the meaning of MVC:

Model-View-Controller, or MVC in short, is a design pattern which describes the responsibilities of certain elements in your program. The responsibilities are as follows:

And with this we have our first problem. See, as it is stated above the View and the Controller should be two separate entities, but what did Apple do? They’ve created the ViewController, an object that needs to do two things at once. Why did they do this? I don’t know for sure, but I bet it’s there for a reason. So, how can we leverage our newly found knowledge?

My thoughts

Well, first of all we might stop worrying so much about a “Massive ViewController”. Before someone can give me an actual number (of lines) on when a ViewController is considered massive I just don’t care. This doesn’t mean that I use the ViewController for all kinds of tasks, I just don’t see the benefit of fighting the system by using another design pattern that, in the end, is just another form of MVC (yes MVVM, I’m looking at you). Second of all we shouldn’t thrive to free up space in the ViewController by refactoring data out of it that it needs. Do you use a UITableViewController? If you don’t reuse the UITableViewDataSource in another table view, why would you want to refactor it out of the only element that needs it? Just leave it in there, configure your table view and forget about it. What we should do though is make sure that the ViewController doesn’t do any business logic. Are you writing a To-Do list app? Why would you want your Core Data code in the ViewController? Create a simple struct, with static functions for saving and loading your To-Dos. The same goes for the Model. Should it belong into the ViewController? Of course not. The Model should always be used as a standalone object, so it should always be a different type.

Introducing my design pattern

So, how do I design my app hierarchy? Well, I’m a big fan of simplicity. So I always start with modeling all types my app needs. And yes, I create a separate file for each of them, no matter how big or small those types end up being. This way, if I ever need to get rid of, or change one, I don’t have to scroll through all the types in one file. Just find the file you’re looking for and change or delete it and you’re good to go. Next come Manager types. Those take a model, initialize all the instances they need, and store them. Furthermore the Manager is responsible for everything related to the functionality of the app, so sorting, filtering out duplicates, whatever your app needs to do is done in the Manager. Finally the ViewController. It asks the Manager for all the data it needs so it can update its subviews. Do I need to fetch data from an external source? Easy, I just create another Manager which only downloads and hands data to the Manager responsible to deliver the results to the ViewController. Let’s have a look at an arbitrary example:

Model types

struct User {
	let name: String
	let id: Int
}

This is the model. A User represents a user in an arbitrary social media app. Let’s create a Post type which, you might already have guessed it, models a post in the same app.

struct Post {
	let text: String
	let user: User
}

Managers

struct NetworkManager {
	//{parameters} might be anything, like an endpoint URL, HTTP method, HTTP headers and so on. Add completion handler to pass the data received from the API to the next caller.
	static func request({parameters}) {
		//set up URLSession and make request using URLSessionDataTask.
	}
}
struct UserManager {
	static func fetchAllUsers() {
		//Call NetworkManager.request() and pass in parameters to fetch all users from API
	}

	static func fetchUser(with userID: Int) {
		//Call NetworkManager.request() and pass in the parameters to fetch a user with a certain ID
	}

	//Additional functions to retrieve more user info, like follower count etc.
}
struct PostManager {
	static func fetchAllPosts() {
		//Call NetworkManager.request() and pass in parameters to fetch all posts from the API
	}

	static func fetchPosts(for user: User) {
		//Call NetworkManager.request() and pass in parameters to fetch all posts of a specific user
	}

	//Additional functions to retrieve more posts, like posts with a certain hashtag etc.
}

ViewControllers

final class PostViewController: UITableViewController {

	var posts = [Post]() 	//This could also be part of the PostManager

	override func viewDidLoad() {
		super.viewDidLoad()

		PostManager.fetchAllPosts() { [unowned self] posts in
			self.posts = posts
			self.tableView.reloadData()
		}
	}

	override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
		let user = posts[indexPath].user

		PostManager.fetchPost(for: user) { [unowned self] post in
			print(post.text)
		}
	}
}
final class UserViewController: UITableViewController {

  var users = [User]()

  override func viewDidLoad() {
    super.viewDidLoad()

    UserManager.fetchAllUsers() { [unowned self] users in
      self.users = users
      self.tableView.reloadData()
    }
  }

  override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let user = users[indexPath]

    UserManager.fetchUser(with: user.id) { [unowned self] user in
      print(user.name)
    }
  }
}

That’s it. With this rather simple approach you’ve got a fully functional app, that might not completely conform to MVC, but is so easily structured that it’s almost impossible to have the “wrong” kind of data anywhere. I think I could prove here that simplicity sometimes makes more sense than trying to refactor yourself to insanity. Of course you could even go further and use one generic Manager type for all entities in your app, but even then the structure would be the same.

Conclusion

Design patterns aren’t always easy to implement, especially in an environment that doesn’t even give you the ability to do so. In my humble opinion it’s also not always necessary to look for a specific design pattern to conform to. I would rather suggest you find a structure that makes sense to you, even if in the end it only makes sense to you. This doesn’t mean that it doesn’t matter where you store which data type, but maybe Managers don’t make sense to you and you’d rather work with Coordinators. Whatever you end up doing just make sure that you don’t fight the system and that you have a clear and concise way of coding that doesn’t break the user’s device.