Skip to content

Instantly share code, notes, and snippets.

@mattgallagher
Last active March 26, 2017 10:56
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattgallagher/b93c6e587488d60a68e4cc0318a7c356 to your computer and use it in GitHub Desktop.
Save mattgallagher/b93c6e587488d60a68e4cc0318a7c356 to your computer and use it in GitHub Desktop.
An abstract look at the View-Model/View-Binding pattern I frequently use in my projects and why this pattern uses the Swift `private` keyword.
// This file is an abstract representation of code that I use extensively in my apps
// for constructing and maintaining views. The reason the `private` keyword is used here
// is to control the interface between two entities (a function and a class) which typically
// reside in the same file.
//
// The function and the class implement a View-Binding and a View-Model. The two are largely
// representations of the *same* concepts – the latter from data perspective and the former
// from a view-infrastructure perspective. Their inter-relatedness makes it highly desirable to
// place them both in the same file – they may share many small types between them and they
// are perpetually co-evolving.
//
// Another reason it is desireable to place them both in the same file is that it is common
// for the smaller implementations to be about 5-10 lines each. In the same way that over-long
// files are difficult to read, so is a project composed of too many trivial-length files.
//
// However, the View-Model is a large blob of state values – some of which are *safe* to bind
// to views are some of which are not. Herein lies the need for the `private` keyword.
//
// The existence of a `private` keyword allows enforcement of rules about safe/not-safe for
// entities in a single file. Without the `private` keyword, the programmer needs greater
// discipline and needs to have a clearer picture of how everything is supposed to fit together
// to avoid making mistakes. And accidentally violating an interface on a state machine isn't
// really the sort of problem that's easy to test.
//
// Without a `private` keyword, you must either:
//
// a) rely on comments to enforce interface safety rules (bad, bad, bad)
//
// b) promote the View-Model from `fileprivate` to `internal` and similarly promote every
// other type shared between View-Model and View-Binding to `internal` (this can be a large
// number of tiny little types when using command-pattern communication between the two)
// and move the View-Binding to a separate file.
//
// So (b) is less bad than (a) but is aesthetically inelegant and leads to large amounts of
// "bouncing" (flipping between the two in an editor) since, as I said, the two naturally
// "co-evolve" so changes to the ViewModel normally require refactoring the ViewBinding and
// vice-versa.
//
// Compared to (a), the `private` keyword enables safer code.
// Compared to (b), the `private` keyword enables more-aesthetic, simpler project structures
// that are easier to inline and/or faster to compile.
//
// Removing smaller-than-file access modifier granularity from Swift will necessarily hurt
// on one of these points, if not both.
fileprivate class MyViewModel: ViewModel {
// The view model maintains a connect to the dataModel. This is not private.
var dataModel: DataModel
init(dataModel: DataModel) {
(input, output) = SignalViaModel(dataModel: dataModel)
}
// The view can directly set this var
var viewSettableState = Something()
// This is a side-effect (maybe dependent state or a timer or file watcher or something)
// and should not be directly accessed by the view
private var internallyMaintainedState = SomethingElse()
// This variable can be safely accessed by the view for data binding purposes
var output: SignalMulti<Whatever>
// This variable is private (direct binding of an input to the view might result in
// premature closing of the signal)
private var input: SignalInput<Whatever>
// The view should use this function to send values to the `input`
func setInternallyPropagatedValue(some: Whatever) {
input.send(value: some)
}
}
// This function is the View-Binding. View-Binding is the construction of the view and the
// connecting of its actions and data observers to the view model.
public func constructMyView(dataModel: DataModel) -> MyView {
let viewModel = MyViewModel(dataModel: dataModel)
let myView = // constructed in code or from NIB file
myView.getTextField().bindTo(viewModel.viewSettableState)
myView.getButton().setAction { v in viewModel.setInternallyPropagatedValue(some: v) }
return myView
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment