Изобретателям MulticastDelegate посвящается. В этой статье речь пойдёт о шаблоне проектирования "Наблюдатель" (Observer) и его реализации на Swift. Точнее о его идеальной реализации, то есть, обеспечивающей: Рассылку уведомлений "один ко многим" Универсальность Взаимодействие компонентов посредством протоколов Безопасность (генерация уведомления возможна только из источника события) Возможность отключения от рассылки Слабую связность компонентов Бескомпромиссное удобство использования Эффективность Компактность Кроссплатформенность Как известно, шаблон Наблюдатель реализует одностороннюю рассылку уведомлений, или связь "один ко многим". Говоря по простому, источник одним вызовом функции, инициирует вызов нескольких соответствующих функций, по одной на каждом Приёмнике. В нашем случае, Источник будет выставлять сущность Событие, которое предоставляет возможность подключаться одному и более Приёмникам через слабую связь. После подключения, Источник (и только он) генерирует Уведомление, которое доставляется всем Приёмникам. В качестве традиционной реализации шаблона Наблюдатель можно указать класс UIControl в iOS SDK. В этом классе имеется метод addTarget(target: Any?, action: Selector, for controlEvents: UIControl.Event), который подключает Приёмник к рассылке уведомлений. Так же имеется возможность отключить Приёмник методом removeTarget(Any?, action: Selector?, for: UIControl.Event). Указанное решение обладает следующими недостатками: Узкая специализация. Источником может быть только UI-элемент из UIKit Ограниченный набор возможных уведомлений : UIControl.Event Платформенная зависимость: iOS Целевой класс должен быть наследником NSObject. Традиционным универсальной реализацией шаблона Наблюдатель на платформах Apple является инструмент [NS]NotificationCenter, который, обладает настолько громоздким интерфейсом, что кажется, разработчики стараются избегать его использовать в повседневной деятельности. К тому же использование NotificationCenter является только условно-безопасным в силу того что кто угодно может отправить любое уведомление и для удовлетворения критерию безопасности придётся прикручивать костыли с упаковкой специфических данных в [NS]Notification. Других вариантов шаблона Наблюдатель стандартная библиотека Swift, кажется, не предоставляет. Постараемся восполнить этот досадный пробел и попытаемся создать идеального Наблюдателя Swift. Для начала отметим, что наш Наблюдатель будет лежать в отдельно SPM-пакете и по этому весь доступный функционал будет помечаться как public, а недоступный - как internal. Итак представляем базовые сущности идеального Наблюдателя: Из объявления нашего протокола следует несколько выводов: Все события меют ровно один параметр, даже если он имеет тип Void. В случае необходимости передавать несколько параметров их надо объеденить в структуру или аналог. Добавление Приёмника осуществляется применением оператора +=. Это сделано для компактной формы записи. Все Приёмники наследуют классу EventObserver. Возникает вопрос: Как удалять Приёмники? Первая очевидная мысль: "Почему бы не применить оператор -= ?". Отвечаем: Для того чтобы удалить EventObserver его надо сначала найти. Для этого его, как минимум, надо сделать Equatable типом. Но предположим что он хранит в себе целевой объект и целевой метод. Сравнивать объекты мы можем, например по значению ссылки (===). А вот сравнивать методы - увы, Swift не позволяет. Следовательно, удалять Приёмники на основе их сравнения мы не можем. Но мы можем хранить слабую ссылку на Приёмника и удалять эту ссылку из списка слушателей в случае удаления целевого объекта (или его представителя). Удаление будет происходить в процессе рассылки следующего (после удаления) уведомления. Это и будет нашим способом удаления Приёмника из списка. У искушенного читателя может возникнуть ещё один вопрос: "EventProtocol содержит associatedtype и упоминает Self, значит мы не можем просто объявить переменную такого типа. Зачем тогда он вообще нужен?" Ответ:  Для того чтобы мы могли раширять его функционал, например добавляя новые перегрузки оператора +=, и эти расширения автоматически будут распространяться на все реализующие EventProtocol сущности. Чем мы в дальнейшем и займёмся. Представим теперь реализацию протокола EventProtocol Разберём что происходит внутри Event: Внутри хранится связанный список обработчиков. Выбор связанного списка обусловлен тем что к нему применяется только две операции: добавление в начало нового элемента (Сложность О(1)) и последовательный проход по элементам, с удалением пустых (тех которые вернули false при вызове handle()). Как известно, удаление элемента из связанного списка имеет сложность O(1). Таким образом, проход по списку с удалением пустых элементов имеет сложность О(n). Быстрее и быть не может. Кроме прохода по списку, внутри Event встроено уведомление о подключении первого Приёмника и удалении последнего. Первое происходит посредством передачи в конструктор опционального connectionNotifier, второе при вызове notifyObservers. Это позволяет владельцу события - Источнику, быть в курсе о подключении первого (и отключении последнего) Приёмника к Событию. Обратим внимание, что метод notifyObservers объявлен internal и не позволяет вызывать рассылку события извне пакета SwiftObserver. Это сделано специально, чтобы отправлять событие мог только Источник - владелец События. Источник вовне выставляет Event, внутри себя инкапсулирует EventSource(разберём ниже), который и предоставляет публичный метод для генерации события. Отметим, что EventSource не является наследником Event, следовательно, невозможно программно преобразовать Event в EventSource, чем и достигается заявленная безопасность. Наконец, представим структуру EventSource: Как видим, EventSource это просто обёртка (wrapper) вокруг Event, предоставляющая владельцу возможность выставлять наружу Event и генерировать уведомления о событии. Теперь перейдём к Приёмнику. Как мы помним, все Приёмники наследуют классу EventObserver. Приведём реализацию наблюдателя, доставляющего уведомления методу класса: Ключевой нюанс в данном классе - хранение целевого объекта target по слабой ссылке. Это создаст слабую зависимость от target, которая разрывается в случае удаления последнего. Приведём завершенный пример использования нашей конструкции: Как мы и хотели (И как гласит буква I акронима SOLID), взаимодействие компонентов организовано посредством протокола Subject. Въедливый читатель заметит: "Ага! Мы хотим обработчиком события без параметров видеть func onVoid() а не func onVoid(_: Void). Где тут заявленная бескомпромиссность удобства!?". Ок, тогда Observer придётся немного усложнить: Ну вот, сами напросились. Зато теперь, как вы и хотели, обработчик принимает методы без параметров в любом виде. Следующий вопрос, который мы ожидаем от читателя звучит примерно так: "Ок, если целевой класс разрушается, то связь разрывается. А мы хотим чтобы была возможность разрывать связь без разрушения целевого объекта". Для этого мы вводим сущность посредника (Mediator) Observer.Link, которая материализует разрываемую связь между Источником и Приёмником.  Здесь уже сразу мы добавили поддержку методов без параметров. Обратите внимание, в дополнение к Observer.Link пришлось написать расширение к исходному протоколу EventProtocol. Приведём пример использования. Инициализация опущена: Ура! Наш Наблюдатель почти завершен! Но, кажется, я слышу гневные возмущения: "Позвольте, мы хотим видеть обработчиками событий замыкания! Нам так не хватало этого в UIControl! Без замыканий мы этим не будем пользоваться!!!".  Что поделать, придётся добавлять Приёмник-замыкание. Хотя они и создают угрозу возникновения циклов сильных ссылок (strong reference cycle). Казалось бы, для использования Приёмника-замыкания нам придётся писать в коде что-то вроде subject.eventVoid += ObserverClosure<Void>() { ... } Но к нашему счастью, Swift умеет выводить тип параметра оператора включая дженерик-часть из конструктора, который в случае единственного аргумента-замыкания можно и не писать совсем. Магия 80-го уровня! В итоге пример с замыканием выглядит следующим образом: <Бурные и продолжительные аплодисменты>Ещё раз напоминаю про опасность возникновения сильных циклов при использовании замыканий. К сожалению, подобное решение создаёт постоянную неразрывную связь, и по этому применимо только при создании жестких композитов(Composite), которые впоследствии уничтожаются целиком. Но к счастью, и в случае применения Приёмника-замыкания,  применим подход с созданием обнуляемого посредника. Увы, в данном случае уже не избежать упоминания полного класса, хотя параметр дженерика по прежнему выводится: Ну вот теперь, кажется, всё!  Мы рассмотрели идеальную, по нашему представлению, реализацию шаблона Наблюдатель на Swift. Проверим, соответсвует ли она заявленным исходно обещаниям: Рассылка уведомления множеству приёмников. Сделано. Универсальность. Мы считаем, что наше решение абсолютно универсально несмотря на возможность отправлять максимум только один параметр. Ничто нам не мешает объединять праметры в структуры. А может быть лучше передавать протокол, у которого Приёмник будет сам запрашивать интересующие его данные. Взаимодействие посредством протоколов. Сделано, показано в первом же примере. Безопасность. Как мы и обещали, мы исключили возможность генерации уведомления извне Источника. Возможно отключения от рассылки. Реализована посредством либо удаления Приёмника, либо при подключении через Link - удаляемой связи. Слабая связность компонентов. У нас она везде кроме подключения Приёмника в виде замыкания. Но вы сами просили! Удобство прежде всего. ) Удобство использования. Ды, мы пошли на жертвы и даже усложнили реализацию чтобы вы могли подключать методы без параметров в виде func() вместо func(_: Void). Ну и опять же, подключение Приёмников-замыканий, куда же без них! И не забываем про возможность уведомления Источника о подключении первого Приёмника и отключение последнего. Эффективность. Подключение слушателя: O(1) - добавление в начало списка. Удаление Приёмника: О(1) - мы удаляем слушателей в процессе рассылки уведомлений. Рассылка уведомления - О(n), где n - количество слушателей. Компактность. Всё решение заняло ~250 строк кода с комментариями. Кажется, да! Кроссплатформенность. Предложенное решение не использует ни Foundation ни какие-либо другие внешние библиотеки. Следовательно, оно применимо на любой платформе, поддерживающей Swift. Спасибо за внимание. P.S.: Весь представленный здесь код доступен в GitHubПокрытие тестами на момент публикации 96.6% P.P.S: Поступали просьбы добавить возможность доставлять уведомление на указанную DispatchQueue. Добавил отдельным пакетом, поскольку там уже зависимость от платформенного Foundation. Ссылка.