Swift architectures


My notes on different swift architectures

MVC:

Model

Models store your data, such as the names of products in a store
This is where the data resides. Things like persistence, model objects, parsers, managers, and networking code live there.

View

Views render data for users, for example a table showing the list of products available
The View layer is the face of your app. It’s classes are often reusable as they don’t contain any domain-specific logic. For example, a UILabel is a view that presents text on the screen, and it’s reusable and extensible.

Controller

Controllers combine the two, by querying the model and converting its data to something views can show
The Controller mediates between the view and the model via the delegation pattern. In an ideal scenario, the controller entity won’t know the concrete view it’s dealing with. Instead, it will communicate with an abstraction via a protocol. A classic example is the way a UITableView communicates with its data source via the UITableViewDataSource protocol. Both data flows (Model,View) go through the Controller, which in turn decide what should happen with the data.

MVC Example:

Controller
class Controller {
	let model: Model
	let view: View
	init() {
		self.model = .init(data: ["a", "b", "c"], onDataChange: onModelChange)
		self.view = .init()
		self.view.tableDelegate = self
		self.view.tableDataSource = model
	}
}
extension Controller {
	func onModelChange() {
		view.update()
	}
	func tableView(tableView: UITableView, didSelectRowAt: IndexPath) {
		print("Tapped: \(didSelectRowAt)")
	}
}
view
class View: UITableView {
	init(){
		// Register cells
		// Set table style
	}
}
extension View {
  func update() {
	  self.reloadTable()
  }
}
Model
class Model {
	var onDataChange: OnDataChange = { print("no callback attached") }
	let data: [String]
	init(data: [String]) {
		self.data = data
	}
}
extension Model: UITableViewDataSource {
  typealias OnDataChange = () -> Void
  func setData(data: [String]){
	  self.data = data
	  onDataChange()
  }
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
	  return data.count
  }
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
	  let cell: UITableViewCell = view.dequeueReusableCell(withIdentifier: "id", for: indexPath)
	  cell.text = "\(data[indexPath.row])"
  }
}

Another approach could be to use a delegate instead of the callbacks

protocol ModelDelegate {
	func handleDataChange()
}
class Model {
	weak var delegate: ModelDelegate? // Setting the variable is weak is important, as the delegate may be deallocated before the model
}
extension Model {
	func setData(data: [String]) {
		self.data = data
	  delegate.handleDataChange()
	}
}
extension ViewController: ModelDelegate {
	func handleDataChange() {
		view.update()
	}
}

Gotchas:

  • Models should ideally be simple data containers that don’t contain much (if any) logic.