Model controllers

The next step is to extract validation and networking logic into their own controllers. That way, we can easily extend their features, while maintaining a simple API surface:

class PostMessageNetworking {
func post(message: String,
callback: @escaping (Bool, Error?) -> Void) {
/* original call */
}
}

class MessageValidator {
enum ValidationError: Error {
case emptyMessage
}

func validate(message: String) -> ValidationError? {
if message.count == 0 {
return .emptyMessage
}
return nil
}
}

We extracted both features, validation and networking, into two independent controllers for the model layer. As you can see, none of them interact with the view layer. Their interfaces are simple: one has a callback, the other returns an optional Error.

All of the logic is done, and we can test every component independently and easily:

The last step is to implement the controller that will tie all those objects together.