Bindings in Swift. Take the first steps to MVVM
Good day. This article will be useful to those who are tired every day of struggling with data volatility in the interface, those who do not yet know the existence of MVVM, and those who doubt that this pattern can be successfully applied in practice when developing iOS applications. Interested, I ask for cat.
At the moment, I can’t imagine how you can safely introduce MVVM to UIKit. This refers to the very MVVM that I saw in Xamarin Forms and which impressed me so much. Most likely, for this you will have to write a framework on top of UIKit and bind the developer to this framework. We will follow the path of least resistance: we will use what Apple gives us. But at the same time, we will strive for a more declarative description of the UI.
The first and most important thing that attracted me to MVVM was the dynamic linking of ViewModel and View. This allows us to describe business logic in isolation from the presentation. We are already used to describing logic in ViewController. And this is real hell. Let's aim to minimize code in the ViewController. First you need to learn to understand that the state of our ViewModel has changed and this change must be reflected in the UI. Apple offers us to use, for example, KVO. ReactiveCocoa would simplify this task. But we have Swift. And we want to make our decision as simple and clean as possible. Here is how our colleagues suggest solving this problem:
By the way, do not forget about the upcoming release of Reactive Cocoa 3.0 . But while the Bond library is the most suitable for our task. While I was working on what I will show below, Bond was just starting out and did not fit my requirements. Even now, it doesn’t suit them a bit, plus everything seemed to me that the developer somehow complicated everything. I wanted to simplify everything as much as possible. But, to tell the truth, when I got into a dead end while working on my vision of how data should be associated with representations, I kept finding answers in the Bond sources .
Let's start with the small and, at the same time, the most important thing. We need to be able to learn about changes in the state of a variable and somehow react to these changes. Let me remind you that we strive for simplicity and conciseness. And in this case, Swift appears in all its glory. It gives us generics, lambdas with awesome syntax, observable properties. So let's make something out of it.
Now we have the opportunity to monitor the change in value value . In practice, it will look something like this:
Add listener support for our mutable entity. The listener will be an anonymous function, into the argument of which we will pass the new value value .
The addListener method simply adds a handler to its list of listeners, and the bind method does the same, but it immediately calls the added listener and passes it the current value .
Thanks to the use of generics, we do not need to check or cast data types. The compiler will do this for us. For example, in the following case, the code will not be compiled:
The compiler knows that the argument of our listener is of type Int and we cannot assign the value of this argument to the text field of the object of the UILabel class , since the type of this field is String . Moreover, thanks to the simplified syntax of anonymous functions, we were able to add listeners without too much writing. But there is no limit to perfection. We can define a couple of operators, or overload the ones available with the goal of further reducing the code.
Agree, it’s unpleasant to constantly describe the same lambdas for linking text and UILabel. I want simplicity:
Nothing is impossible. After all, we can easily come to such a syntax. The implementation idea is again kindly borrowed from Bond . The idea is simple: we will store at the object of some kind a field that has a listener, and we can bind this listener to a dynamic object.
An object of the PropertyModifier class will be created by the view itself, and a lambda will be passed to the constructor with code that changes the value of a certain field of the view.
I note that in extension we cannot describe stored fields, so ObjC Runtime and the functions objc_setAssociatedObject , objc_getAssociatedObject come to the rescue . Now we can do this:
Let's simplify:
Much better. But that is not all. We can highlight some of the most used property of the view and assign it the PropertyModifier by default.
That's all. UILabel has got a standard PropertyModifier that changes the values of the text field . And we came to the appointed goal, namely, we can create a relationship as follows:
One of the noteworthy concepts in Xamarin Forms that I liked was the teams. In fact, we can describe a command using two functions: one returns true or false , indicating that the command can be executed, and the second - the action that the command performs. Let's say we have a button ( UIButton ). The button has an enabled field , the button can be pressed by the user, after which some action should happen. Remember that we strive for declarative descriptions of interface behavior? So let's extend this idea to our controls.
So, we have a command that has an enabled field and a function that must be executed when the execute method is called . We must associate our team with a button. To do this, we entered the Commander protocol with the setCommand method . We implement our protocol for UIButton by associating the dynamic field of the enabled command with the corresponding UIButton property . We also overloaded the >> operator for convenience. What do we get as a result:
At our disposal appeared dynamic objects that we can associate with anything. We now have commands that allow us to describe the action by clicking on the button more expressively. And this is enough to simplify our UIViewController . Behind the scenes were map and filter for Dynamic , bidirectional binders and simplified work with UITableView . But you can take a look at this yourself. A project demonstrating the capabilities of this approach is available on GitHub . I recommend taking a look at it.
Thanks for attention. Comments, suggestions and criticism are welcome.
Background with a bunch of lyrics
I can’t call myself an experienced iOS developer. Although the acquaintance with the iOS world took place several years ago, the desire to pump in the development of applications for iOS appeared recently. My path was thorny. Obj-C was not immediately impressed, I wanted to develop applications on something familiar. Therefore, PhoneGap, Apcelerator Titanium were used, and that’s all. But, naturally, nothing came of these undertakings. After a long break, the company I work for has seriously considered developing a mobile application. I didn’t invent anything and simplify my life - I performed work exclusively on ObjC without using third-party frameworks. And it was a pain for me. Simple things turned out to be complicated, I could not cope with autolayout, it was impossible to look at the code. Therefore, in the next project, Xamarin Forms was launched. Having worked on the project for about two months, it became clear that this technology is still far from perfect (in the end it turned out that the project was in beta status, but this was not mentioned much). But while working with Xamarin Forms, I was inspired by many patterns that this project was saturated with; moreover, I had to make a bunch of custom components, which led to a clearer understanding of the work of UIKit. At that moment, when it became clear that our project should be rewritten to native, Swift was rapidly approaching the release. I read a book on this language and felt strong in myself to start all over again. But the first experience was still reminiscent of myself, so I began to dig towards MVVM in iOS. I really liked this concept. that the project was in beta status, but this was not mentioned much). But while working with Xamarin Forms, I was inspired by many patterns that this project was saturated with; moreover, I had to make a bunch of custom components, which led to a clearer understanding of the work of UIKit. At that moment, when it became clear that our project should be rewritten to native, Swift was rapidly approaching the release. I read a book on this language and felt strong in myself to start all over again. But the first experience was still reminiscent of myself, so I began to dig towards MVVM in iOS. I really liked this concept. that the project was in beta status, but this was not mentioned much). But while working with Xamarin Forms, I was inspired by many patterns that this project was saturated with; moreover, I had to make a bunch of custom components, which led to a clearer understanding of the work of UIKit. At that moment, when it became clear that our project should be rewritten to native, Swift was rapidly approaching the release. I read a book on this language and felt strong in myself to start all over again. But the first experience was still reminiscent of myself, so I began to dig towards MVVM in iOS. I really liked this concept. which led to a clearer understanding of the work of UIKit. At that moment, when it became clear that our project should be rewritten to native, Swift was rapidly approaching the release. I read a book on this language and felt strong in myself to start all over again. But the first experience was still reminiscent of myself, so I began to dig towards MVVM in iOS. I really liked this concept. which led to a clearer understanding of the work of UIKit. At that moment, when it became clear that our project should be rewritten to native, Swift was rapidly approaching the release. I read a book on this language and felt strong in myself to start all over again. But the first experience was still reminiscent of myself, so I began to dig towards MVVM in iOS. I really liked this concept.
At that time, all the articles that caught my eye suggested solving the problem using ReactiveCocoa. Looking at the code examples of this library, I realized that I still have to study and study, because I didn’t understand anything in what I saw. For Swift, they also suggested using ReactiveCocoa. Actually, an article by Colin Eberhardt was my starting point. But soon I had to face the fact that the approach described by the aforementioned author led to memory leaks. Apparently I was doing something wrong and then did not understand what exactly. Plus, ReactiveCocoa remained a black box for me. It was decided to get rid of this library, given that it was used only to link view models with view. Stumbled upon an Observable Swift projectthat solved the binding problem. Soon our project was completed, and on the horizon a new one, and I wanted to thoroughly prepare for it.
At that time, all the articles that caught my eye suggested solving the problem using ReactiveCocoa. Looking at the code examples of this library, I realized that I still have to study and study, because I didn’t understand anything in what I saw. For Swift, they also suggested using ReactiveCocoa. Actually, an article by Colin Eberhardt was my starting point. But soon I had to face the fact that the approach described by the aforementioned author led to memory leaks. Apparently I was doing something wrong and then did not understand what exactly. Plus, ReactiveCocoa remained a black box for me. It was decided to get rid of this library, given that it was used only to link view models with view. Stumbled upon an Observable Swift projectthat solved the binding problem. Soon our project was completed, and on the horizon a new one, and I wanted to thoroughly prepare for it.
Formulation of the problem
At the moment, I can’t imagine how you can safely introduce MVVM to UIKit. This refers to the very MVVM that I saw in Xamarin Forms and which impressed me so much. Most likely, for this you will have to write a framework on top of UIKit and bind the developer to this framework. We will follow the path of least resistance: we will use what Apple gives us. But at the same time, we will strive for a more declarative description of the UI.
The first and most important thing that attracted me to MVVM was the dynamic linking of ViewModel and View. This allows us to describe business logic in isolation from the presentation. We are already used to describing logic in ViewController. And this is real hell. Let's aim to minimize code in the ViewController. First you need to learn to understand that the state of our ViewModel has changed and this change must be reflected in the UI. Apple offers us to use, for example, KVO. ReactiveCocoa would simplify this task. But we have Swift. And we want to make our decision as simple and clean as possible. Here is how our colleagues suggest solving this problem:
- Exploring KVO Alternatives With Swift
- Bindings, Generics, Swift and MVVM
- Solving the binding problem with Swift
- Swift bond
By the way, do not forget about the upcoming release of Reactive Cocoa 3.0 . But while the Bond library is the most suitable for our task. While I was working on what I will show below, Bond was just starting out and did not fit my requirements. Even now, it doesn’t suit them a bit, plus everything seemed to me that the developer somehow complicated everything. I wanted to simplify everything as much as possible. But, to tell the truth, when I got into a dead end while working on my vision of how data should be associated with representations, I kept finding answers in the Bond sources .
Dynamic
Let's start with the small and, at the same time, the most important thing. We need to be able to learn about changes in the state of a variable and somehow react to these changes. Let me remind you that we strive for simplicity and conciseness. And in this case, Swift appears in all its glory. It gives us generics, lambdas with awesome syntax, observable properties. So let's make something out of it.
class Dynamic {
init(_ v: T) {
value = v
}
var value: T {
didSet {
println(value)
}
}
}
Now we have the opportunity to monitor the change in value value . In practice, it will look something like this:
let dynamicInt: Dynamic = Dynamic(0)
println(dynamicInt.value)
dynamicInt.value = 1
dynamicInt.value = 17
Add listener support for our mutable entity. The listener will be an anonymous function, into the argument of which we will pass the new value value .
class Dynamic {
typealias Listener = T -> ()
private var listeners: [Listener] = []
init(_ v: T) {
value = v
}
var value: T {
didSet {
for l in listeners { l(value) } }
}
func bind(l: Listener) {
listeners.append(l)
l(value)
}
func addListener(l: Listener) {
listeners.append(l)
}
}
The addListener method simply adds a handler to its list of listeners, and the bind method does the same, but it immediately calls the added listener and passes it the current value .
let dynText: Dynamic = Dynamic("")
dynText.bind { someLabel.text = $0 }
dynText.addListener { otherLabel.text = $0 }
dynText.value = "New text"
Thanks to the use of generics, we do not need to check or cast data types. The compiler will do this for us. For example, in the following case, the code will not be compiled:
let dynInt: Dynamic = Dynamic(0)
dynInt.bind { someLabel.text = $0 }
The compiler knows that the argument of our listener is of type Int and we cannot assign the value of this argument to the text field of the object of the UILabel class , since the type of this field is String . Moreover, thanks to the simplified syntax of anonymous functions, we were able to add listeners without too much writing. But there is no limit to perfection. We can define a couple of operators, or overload the ones available with the goal of further reducing the code.
func >> (left: Dynamic, right: T -> Void) {
return left.addListener(right)
}
infix operator >>> {}
func >>> (left: Dynamic, right: T -> Void) {
left.bind(right)
}
let dynText: Dynamic = Dynamic("")
dynText >>> { someLabel.text = $0 }
dynText >> { otherLabel.text = $0 }
dynText.value = "New text"
Thoughts on unowned, weak, Void and Void?
In practice, the examples described above will lead to memory leaks. Here is an example:
Obviously, now the listener function and self are rigidly connected to each other and the MyViewController class object will never be deleted . To prevent this from happening, you need to loosen the connection:
That's better. But there is one thing. There is no guarantee that the listener function will not be called after the removal of the MyViewController object . To protect ourselves, we use weak :
But in this case, the code will not be compiled, because our listener is of type String -> Void? , but must be of type String -> Void for successful compilation. Therefore, I initially added two types of listeners to Dynamic : with the return values Void and Void? .. Accordingly, I overloaded the bind and addListener methods to support two types of listeners. But it soon became clear that the compiler cannot determine which method to call, if, for example, this is done:
Therefore, I had to abandon the idea of supporting two types of listeners and take advantage of such tricks:
Of course, one could refuse to use weak in favor of passing a dynamic object, in addition to the handler function, also a reference to the object and not call the function if the object suddenly turned out to be deleted. This is the approach used in the Bond library . But that was not my way :)
class MyViewController: UIViewController {
@IBOutlet weak var label: UILabel!
let viewModel = MyViewModel()
override func viewDidLoad() {
viewModel.someText >>> { self.label.text = $0 }
super.viewDidLoad()
}
}
Obviously, now the listener function and self are rigidly connected to each other and the MyViewController class object will never be deleted . To prevent this from happening, you need to loosen the connection:
viewModel.someText >>> { [unowned self] in self.label.text = $0 }
That's better. But there is one thing. There is no guarantee that the listener function will not be called after the removal of the MyViewController object . To protect ourselves, we use weak :
viewModel.someText >>> { [weak self] in self?.label.text = $0 }
But in this case, the code will not be compiled, because our listener is of type String -> Void? , but must be of type String -> Void for successful compilation. Therefore, I initially added two types of listeners to Dynamic : with the return values Void and Void? .. Accordingly, I overloaded the bind and addListener methods to support two types of listeners. But it soon became clear that the compiler cannot determine which method to call, if, for example, this is done:
viewModel.someText >>> { [weak self] in if self != nil { self!.label.text = $0 } }
Therefore, I had to abandon the idea of supporting two types of listeners and take advantage of such tricks:
viewModel.someText >>> { [weak self] in self?.label.text = $0; return }
viewModel.someText >>> { [weak self] in self?.label.text = $0; () }
viewModel.someText >>> { [weak self] v in v; self?.label.text = v }
Of course, one could refuse to use weak in favor of passing a dynamic object, in addition to the handler function, also a reference to the object and not call the function if the object suddenly turned out to be deleted. This is the approach used in the Bond library . But that was not my way :)
Simplification of work with UIKit
Agree, it’s unpleasant to constantly describe the same lambdas for linking text and UILabel. I want simplicity:
viewModel.someText >>> label
Nothing is impossible. After all, we can easily come to such a syntax. The implementation idea is again kindly borrowed from Bond . The idea is simple: we will store at the object of some kind a field that has a listener, and we can bind this listener to a dynamic object.
final class PropertyModifier {
typealias Modifier = (T) -> ()
let modifier: Modifier
init (_ l: Modifier) {
self.modifier = l
}
}
An object of the PropertyModifier class will be created by the view itself, and a lambda will be passed to the constructor with code that changes the value of a certain field of the view.
private var UILabelPropertyKeyTextModifier: UInt8 = 0
extension UILabel {
var textModifier: PropertyModifier {
if let pm: AnyObject = objc_getAssociatedObject(self, &UILabelPropertyKeyTextModifier) {
return pm as PropertyModifier
} else {
let pm = PropertyModifier { [weak self] in self?.text = v; () }
objc_setAssociatedObject(self, &UILabelPropertyKeyTextModifier, pm, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
return pm
}
}
}
I note that in extension we cannot describe stored fields, so ObjC Runtime and the functions objc_setAssociatedObject , objc_getAssociatedObject come to the rescue . Now we can do this:
viewModel.someText >>> label.textModifier.modifier
Let's simplify:
func >> (left: Dynamic, right: PropertyModifier) {
left.addListener(right.modifier)
}
func >>> (left: Dynamic, right: PropertyModifier) {
left.bind(right.modifier)
}
viewModel.someText >>> label.textModifier
Much better. But that is not all. We can highlight some of the most used property of the view and assign it the PropertyModifier by default.
protocol BindableObject {
typealias DefaultPropertyModifierTargetType
var defaulPropertytModifier: PropertyModifier { get }
}
extension UILabel: BindableObject {
typealias DefaultPropertyModifierTargetType = String?
var defaulPropertytModifier: PropertyModifier {
return textModifier
}
}
func >> (left: Dynamic, right: B) {
left.addListener(right.defaulPropertytModifier.modifier)
}
func >>> (left: Dynamic, right: B) {
left.bind(right.defaulPropertytModifier.modifier)
}
That's all. UILabel has got a standard PropertyModifier that changes the values of the text field . And we came to the appointed goal, namely, we can create a relationship as follows:
viewModel.someText >>> label
Teams
One of the noteworthy concepts in Xamarin Forms that I liked was the teams. In fact, we can describe a command using two functions: one returns true or false , indicating that the command can be executed, and the second - the action that the command performs. Let's say we have a button ( UIButton ). The button has an enabled field , the button can be pressed by the user, after which some action should happen. Remember that we strive for declarative descriptions of interface behavior? So let's extend this idea to our controls.
final class Command {
typealias CommandType = (value: T, sender: AnyObject?) -> ()
weak var enabled: Dynamic?
private let command: CommandType
init (enabled: Dynamic, command: CommandType) {
self.enabled = enabled
self.command = command
}
init (command: CommandType) {
self.command = command
}
func execute(value: T) {
execute(value, sender: nil)
}
func execute(value: T, sender: AnyObject?) {
var enabled = true
if let en = self.enabled?.value { enabled = en }
if enabled { command(value: value, sender: sender) }
}
}
protocol Commander {
typealias CommandType
func setCommand(command: Command)
}
func >> (left: B, right: Command) {
left.setCommand(right)
}
private var UIButtonPropertyKeyCommand: UInt8 = 0
extension UIButton: Commander {
typealias CommandType = ()
func setCommand(command: Command) {
if let c: AnyObject = objc_getAssociatedObject(self, &UIButtonPropertyKeyCommand) {
fatalError("Multiple assigment to command")
return
}
objc_setAssociatedObject(self, &UIButtonPropertyKeyCommand, command, objc_AssociationPolicy(OBJC_ASSOCIATION_ASSIGN))
command.enabled?.bind { [weak self] in self?.enabled = $0; () }
addTarget(self, action: Selector("buttonTapped:"), forControlEvents: .TouchUpInside)
}
func buttonTapped(sender: AnyObject?) {
if let c: Command = objc_getAssociatedObject(self, &UIButtonPropertyKeyCommand) as? Command {
c.execute((), sender: sender)
}
}
}
So, we have a command that has an enabled field and a function that must be executed when the execute method is called . We must associate our team with a button. To do this, we entered the Commander protocol with the setCommand method . We implement our protocol for UIButton by associating the dynamic field of the enabled command with the corresponding UIButton property . We also overloaded the >> operator for convenience. What do we get as a result:
class PageModel {
let nextPageEnabled: Dynamic = Dynamic(true)
lazy var openNextPage: Command<()> = Command (
enabled: self.nextPageEnabled,
command: {
[weak self] value, sender in
//Open next page
})
}
class MyViewController: UIViewController {
@IBOutlet weak var nextButton: UIButton!
let pageModel = PageModel()
override func viewDidLoad() {
nextButton >> pageModel.openNextPage
super.viewDidLoad()
}
}
Conclusion
At our disposal appeared dynamic objects that we can associate with anything. We now have commands that allow us to describe the action by clicking on the button more expressively. And this is enough to simplify our UIViewController . Behind the scenes were map and filter for Dynamic , bidirectional binders and simplified work with UITableView . But you can take a look at this yourself. A project demonstrating the capabilities of this approach is available on GitHub . I recommend taking a look at it.
A couple of examples for seed
class TwoWayBindingPage: Page {
typealias PMT = TwoWayBindingPageModel
@IBOutlet weak var switchLabel: UILabel!
@IBOutlet weak var switchControl: UISwitch!
@IBOutlet weak var switchButton: UIButton!
@IBOutlet weak var textFieldLabel: UILabel!
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var textFieldButton: UIButton!
@IBOutlet weak var sliderLabel: UILabel!
@IBOutlet weak var slider: UISlider!
@IBOutlet weak var sliderButton: UIButton!
override func bindPageModel() {
super.bindPageModel()
let pm = pageModel as PMT
switchButton >> pm.changeSomethingEnabled
textFieldButton >> pm.changeUserName
sliderButton >> pm.changeAccuracy
pm.somethingEnabled | { "Current dynamic value: \($0)" } >>> switchLabel
pm.userName | { "Current dynamic value: \($0)" } >>> textFieldLabel
pm.accuracy | { "Current dynamic value: \($0)" } >>> sliderLabel
pm.somethingEnabled <<>>> switchControl
pm.userName <<>>> textField
pm.accuracy <<>>> slider
}
}
class BeerListPage: Page {
typealias PMT = BeerListPageModel
@IBOutlet weak var tableView: UITableView!
private var tableViewHelper: SimpleTableViewHelper!
override func bindPageModel() {
super.bindPageModel()
let pm = pageModel as PMT
tableViewHelper = SimpleTableViewHelper(tableView: tableView, data: pm.beerList, cellType: BeerTableCell.self, command: pm.openBeerPage)
tableView.pullToRefreshControl >> pm
tableView.infiniteScrollControl >> pm
}
}
Thanks for attention. Comments, suggestions and criticism are welcome.