Паттерн Observer в Swift

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Всем доброго времени суток! В этой статье речь пойдет о паттерне Observer. Все, кто связан с iOS разработкой наверняка сталкивались с инструментами в основе работы которых лежит этот паттерн. Например, NotificationCenter, KVO или великий и могучий RxSwift, который сейчас очень популярен. В этой статье я на простом примере разберу принцип работы данного паттерна. Не ругайтесь, перфекционисты! Эта статья исключительно про паттерн Observer, поэтому для большей наглядности некоторыми принципами код стайла пришлось пренебречь. 

Пример из жизни

Допустим, при просмотре Youtube вы наткнулись на интересный контент и захотели подписаться на этого автора. Вы нажимаете кнопку Subscribe и вуаля! Вы не пропустите ни одного нового видео от понравившегося вам автора. 

Принцип работы паттерна точно такой же. Есть наблюдаемая сущность, и есть те, кто подписываются на ее изменения - подписчики. Причем подписчики мгновенно узнают об изменениях.

Перейдем от теории к практике.

Создаем новый проект. 

Интерфейс выбираем Storyboard, писать будем на Swift.

Открываем файл Main.storyboard

Главный экран будет состоять из двух основных секций: "модуль блогера", с кнопкой при помощи которой он публикует контент и "модуль подписчика", где подписчик видит самое свежее видео от автора, а также может отписаться или подписаться на автора. Итого на сториборд помещаем 4xUILabel, 1XUIButton, 1xUISwitch. Черным цветом указаны UILabel, синим цветом UIButton.

Делаем шрифт пожирнее и устанавливаем констрейнты.

Протягиваем связи от всех элементов кроме заголовков. При зажатой клавише Option нажимаем на ViewController. Отрывается второе окно. При зажатой клавише перетаскиваем связи во ViewController. В итоге должно получиться как на рисунке ниже.

Со сторибордами все, осталось написать код.

Создаем протокол подписчика, это наш Observer.

protocol Subscriber : AnyObject {
    func update(subject : Bloger )
}

Создаем класс Bloger. В нем указываем переменные которые будут хранить последнюю актуальную информацию для наших подписчиков. В нашем случае это название последнего видео и его порядковый номер.

class Bloger {
    var counter : Int = 0
    var lastVideo = ""    
}

 Также, класс должен содержать список подписчиков.

private lazy var subscribers : [Subscriber] = []

Метод, который выполняет подписки на наблюдаемый объект.

func subscribe(_ subscriber: Subscriber) {
        print("subscribed")
        subscribers.append(subscriber)
    }

Метод, который служит для отмены подписки. Логика этого метода в том, что совершается поиск по массиву нужного нам объекта, затем объект из него удаляется.

func unsubscribe(_ subscriber: Subscriber) {
        if let index = subscribers.firstIndex(where: {$0 === subscriber}) {
            subscribers.remove(at: index)
        }
  			print("unsubscribed")
    }

Метод для рассылки уведомлений подписчикам:

func notify() {
        subscribers.forEach { $0.update(subject: self)
        }
    }

Метод для рассылки уведомлений подписчикам:

func notify() {
        subscribers.forEach { $0.update(subject: self)
        }
    }

Ну и напоследок метод, который содержит в себе определенную бизнес логику. В нашем случае это публикация видео от автора и увеличение его номера на единицу с каждым последующим вызовом этого метода.

func releaseVideo() {
        counter += 1
        lastVideo = "video" + "\(counter)"
        notify() //Notify subscribers
  			print("released!")
    }

В итоге у нас получается:

class Bloger {
    
    var counter : Int = 0
    var lastVideo = ""
    
    private lazy var subscribers : [Subscriber] = [] // Создаем массив с подписчиками
    
    func subscribe(_ subscriber: Subscriber) {
        print("subscribed")
        subscribers.append(subscriber)
    }
    
    func unsubscribe(_ subscriber: Subscriber) {
        print("unsubscribed")
        if let index = subscribers.firstIndex(where: {$0 === subscriber}) {
            subscribers.remove(at: index)
        }
    }
    
    func notify() {
        subscribers.forEach { $0.update(subject: self)
        }
    }
    
    func releaseVideo() {
        counter += 1
        lastVideo = "video" + "\(counter)"
        notify()
      	print("released!")
    }
    
}

Осталось совсем чуть-чуть. Подписываем наш ViewController на протокол Subscriber.

В этом методе мы будем обновлять Label на экране, который показывает название последнего полученного видео от блогера.

func update(subject: Bloger) {
        subscriberInfoLabel.text = subject.lastVideo
    }

Создаем экземпляр класса Bloger() .

let bloger = Bloger()

В методе ViewDidLoad подписываемся на этот экземпляр.

bloger.subscribe(self)

Чтобы все заработало, осталось привязать методы для подписки и публикации к нашему интерфейсу. Для этого пропишем следующий код:

 @IBAction func publishButton(_ sender: Any) {
        bloger.releaseVideo()
    }
 @IBAction func subscribeToggle(_ sender: Any) {
        if (sender as AnyObject).isOn {
            bloger.subscribe(self)
        } else {
            bloger.unsubscribe(self)
        }
    }

В итоге у нас получается вот такой код:

import UIKit

class ViewController: UIViewController, Subscriber {
    
    @IBOutlet weak var subscriberInfoLabel: UILabel!
    
    let bloger = Bloger()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        bloger.subscribe(self)
    }

    @IBAction func publishButton(_ sender: Any) {
        bloger.releaseVideo()
    }
    @IBAction func subscribeToggle(_ sender: Any) {
        if (sender as AnyObject).isOn {
            bloger.subscribe(self)
        } else {
            bloger.unsubscribe(self)
        }
    }
    
    func update(subject: Bloger) {
        subscriberInfoLabel.text = subject.lastVideo
    }
}

//MARK:- Protocols
protocol Subscriber : AnyObject {
    func update(subject : Bloger )
}

class Bloger {
    
    var counter : Int = 0
    var lastVideo = ""
    
    private lazy var subscribers : [Subscriber] = [] // Создаем массив с подписчиками
    
    func subscribe(_ subscriber: Subscriber) {
        print("subscribed")
        subscribers.append(subscriber)
    }
    
    func unsubscribe(_ subscriber: Subscriber) {
        print("unsubscribed")
        if let index = subscribers.firstIndex(where: {$0 === subscriber}) {
            subscribers.remove(at: index)
        }
    }
    
    func notify() {
        subscribers.forEach { $0.update(subject: self)
        }
    }
    
    func releaseVideo() {
        counter += 1
        lastVideo = "video" + "\(counter)"
        notify()
        print("released!")
    }
    
}

Запускаем симулятор и проверяем, что вышло!

Надеюсь, теперь вы разобрались с основой работы паттерна Observer, и статья была вам полезна!

Ссылка на итоговый проект

Источник: https://habr.com/ru/post/564820/


Интересные статьи

Интересные статьи

В этом туториале мы освоим паттерн проектирования “Команда” (Command) и реализуем его в Unity в рамках системы перемещения игрового объекта.Запросы, приказы и команды: все мы знакомы с ни...
Есть статьи о недостатках Битрикса, которые написаны программистами. Недостатки, описанные в них рядовому пользователю безразличны, ведь он не собирается ничего программировать.
Добрый день (или вечер, или утро, в зависимости от того, когда выйдет этот пост). Я хочу высказаться о элитарной части программирования, и донести, в общем-то, очевидную мысль до начинающих в ...
В статье описаны необходимые параметры сервера для оптимальной работы сайта на платформе 1С-Битрикс.
Знания — это хорошо, просто отлично. Но нужна еще и практика, чтобы можно было использовать полученные данные, переведя их из статуса «пассивное хранение» в статус «активное использование». К...