Observer Pattern Part #2
In this post, we will start creating the timer. We will create a class that will do this management, which will be called AuctionViewModel.swift
. Let's instantiate it in the ViewController.
class ViewController: UIViewController {
let uiAuction = UIAuction()
let viewModel = AuctionViewModel()
override func loadView() {
self.view = uiAuction
}
override func viewDidLoad() {
super.viewDidLoad()
self.uiAuction.personOne.delegate = self
self.uiAuction.personTwo.delegate = self
self.uiAuction.personThree.delegate = self
}
}
AuctionViewModel.swift
We will use: var finishAuction = 7
to define how long our auction will have to choose who is the winner. To start the timer, let's create a function
func startTimer (completed: @escaping ((Int) -> ()), completed: @escaping (() -> ())) {}
with two completedHandler. The first is used to send the result of the stopwatch decrement to ViewController, and the other is used to notify when the timer is over.
import Foundation
class AuctionViewModel{
var finishAuction = 7
func startTimer(changedTimer: @escaping ((Int)->()), completedTimer: @escaping (()->()) ){
let timerValue = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (timer) in
if(self.finishAuction == 0){
timer.invalidate()
completedTimer()
}else{
self.finishAuction -= 1
}
changedTimer(self.finishAuction)
}
}
}
We have a problem! This function is async when a participant sends a price, and another participant also does the same, we will have two active timers. Solve this, let's create the function func validateTimer (time: Timer) {}
, and create a static variable static var arrayTimer = [Timer] ()
, to add all the Timers that are created.
The function func validateTimer (time: Timer) {}
will be used to add all the timers that are created and keep only one timer running we will also reset the timer.
func validateTimer(time:Timer){
AuctionViewModel.arrayTimer.append(time)
if(AuctionViewModel.arrayTimer.count > 1){
AuctionViewModel.arrayTimer.first?.invalidate()
AuctionViewModel.arrayTimer.removeFirst()
self.finishAuction = 8
}
}
Let's call the function startTimer (...)
.
class AuctionViewModel{
var finishAuction = 7
static var arrayTimer = [Timer]()
func startTimer(changedTimer: @escaping ((Int)->()), completedTimer: @escaping (()->()) ){
let timerValue = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (timer) in
if(self.finishAuction == 0){
timer.invalidate()
completedTimer()
}else{
self.finishAuction -= 1
}
changedTimer(self.finishAuction)
}
//Calls the function
self.validateTimer(time: timerValue)
//
}
func validateTimer(time:Timer){
AuctionViewModel.arrayTimer.append(time)
if(AuctionViewModel.arrayTimer.count > 1){
AuctionViewModel.arrayTimer.first?.invalidate()
AuctionViewModel.arrayTimer.removeFirst()
self.finishAuction = 8
}
}
}
Let's go to ViewController, update our timer on the label.
ViewController.swift
import UIKit
class ViewController: UIViewController {
let uiAuction = UIAuction()
let viewModel = AuctionViewModel()
override func loadView() {
self.view = uiAuction
}
override func viewDidLoad() {
super.viewDidLoad()
self.uiAuction.personOne.delegate = self
self.uiAuction.personTwo.delegate = self
self.uiAuction.personThree.delegate = self
}
}
extension ViewController: SendValueProtocol{
func sendValue(text: String, indentify: String) {
self.viewModel.startTimer { [weak self] (time) in
self!.uiAuction.auctioneer.lbTime.text = String(time)
} completedTimer: {
self.uiAuction.lbwinner.text = indentify
}
}
}
Let's call the timer on our delegate. While the timer is decreasing, the value is being assigned to self! .UiAuction.auctioneer.lbTime.text
, when finished, we will put the name of the winner on the labelself.uiAuction.lbwinner.text = indentify
.
We just need to set the price for all participants in the auction, we will finally use our observer pattern.
Let's create a Bind class.
Bind.swift
import Foundation
protocol Observer {
func update(_ notifyValue: Any)
}
class Subject<T>{
var observers: [Observer] = [Observer]()
//change
var value: T?{
didSet{
if let value = value{
notify(with: value)
}
}
}
//register
func register(_ observer: Observer){
observers.append(observer)
}
//call notify
func notify(with: T){
for observer in observers{
observer.update(value as Any)
}
}
}
Remember that the Observer needs to have two things that define what it is:
- Register
- Notify
So we have two functions. The exclusion function could be created.
This class is generic, you will decide the type of the class. We have a protocol, which will be in accordance with the classes that want to change the value. When changing the value, it calls func notify
and sends the value to everyone who signed the protocol.
AuctionViewModel.swift
class AuctionViewModel{
var bind = Subject<Int>()
//...
//...
//...
}
In our viewModel, we will decide which type of our class, we will use the observer in the price. Usually, the value of a price is of type float or double, we will create it in type Int.
We will go in all classes that we want to change the auction value:
Auctioneer.swift
class Auctioneer: UIView {
//...
}
extension Auctioneer: Observer{
func update(_ notifyValue: Any) {
let value = notifyValue as! Int
self.lbValue.text = String(value)
}
}
We will create an extension to assign the protocol to the class, as the value that will arrive is generic, we have to convert it to type Int.
Person.swift
class Person: UIView {
//...
}
extension Person: Observer{
func update(_ notifyValue: Any) {
let value = notifyValue as! Int
self.lbValue.text = String(value)
}
}
We will use the same strategy as in the Auctioneer class.
ViewController.swift
To work our observer pattern, we need to register the classes that have the protocol.
class ViewController: UIViewController {
let uiAuction = UIAuction()
let viewModel = AuctionViewModel()
override func loadView() {
self.view = uiAuction
}
override func viewDidLoad() {
super.viewDidLoad()
self.uiAuction.personOne.delegate = self
self.uiAuction.personTwo.delegate = self
self.uiAuction.personThree.delegate = self
//Register Observer
self.viewModel.bind.register(self.uiAuction.personOne)
self.viewModel.bind.register(self.uiAuction.personTwo)
self.viewModel.bind.register(self.uiAuction.personThree)
self.viewModel.bind.register(self.uiAuction.auctioneer)
}
}
And notify, so that everyone who contains the protocol receives the value.
extension ViewController: SendValueProtocol{
func sendValue(text: String, indentify: String) {
//Notify observer
self.viewModel.bind.value = Int(text)!
self.viewModel.startTimer { [weak self] (time) in
self!.uiAuction.auctioneer.lbTime.text = String(time)
} completedTimer: {
self.uiAuction.lbwinner.text = indentify
}
}
}
Link to the project GitHub