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