Objective C

Події орієнтована логіка в Objective C тримається на трьох китах - протоколи, notification center і key-value observing. Традиційно протоколи використовуються для розширення функціоналу базових класів без наслідування, key-value observing – для взаємодії між візуальною та логічною частиною програми, а notification center – для обробки подій користувача.

Звісно, ​​все це благообразие можна спокійно використовуватиме побудови складних додатків. Жодної реальної необхідності у винаході власних велосипедів, звичайно ж, немає. Однак мені, як людині, що прийшла в розробку Objective C додатків зі світу .NET, здалося дуже неприємним те, що notification center, який я планував використовувати для подій, розриває стек програми, записуючи подію, що відбулася в чергу в UI thread, а протоколи в класичному поданні не надто зручні, тому для зручності я вирішив спорудити собі механізм, який був би набагато більше схожим на те, чим ми звикли обходитися у світі .NET. Так народилася ідея реалізації моделі множинних підписантів через спеціальний клас, названий AWHandlersList.

Ідея класу досить проста — він містить у собі список підписантів, кожен елемент якого складається з двох компонентів — target та selector.

Навіщо створено цей велосипед? Мені здалося, що він зручніший за всі представлені стандартні моделі для деякої логіки, пов'язаної з передачею подій. Можливо, він комусь допоможе полегшити життя.

У .NET звична модель подієвої логіки пропонує делегат із двома параметрами - sender типу object і args типу, успадкованого від EventArgs. Щоб не ламати собі мозок, зробимо те саме. Для початку, визначимо порожній класEventArgs, від якого успадковуватимуться всі аргументи подій.

Тепер визначимо клас, який міститиме пару «цільовий об'єкт і метод, що викликається», додавши туди заодно деяку налагоджувальну інформацію для того, щоб надалі можна було легко налагоджувати логіку подій.

Як бачите, і target, і method по суті є слабкими посиланнями. Це цілком закономірно — слабкі посилання повсюдно використовуються у світі Objective C для того, щоб уникнути circular references і дати можливість автоматично звільняти об'єкти. На жаль, це призводить до того, що при недбалому кодуванні повсюдно з'являються «мертві» покажчики на об'єкти, які кидають додаток, тому я трохи далі покажу один гарний механізм, який дозволяє попереджати та усувати їхню появу.

Тепер, нарешті, перейдемо до нашого основного класу списку підписантів. У коді є нетривіальні моменти, але вони вирішуються читанням документації, а якщо бажання розбиратися в питанні немає — його можна просто використовувати, код повністю робочий і вийнятий з «бойового» проекту.

Коротко поясню, навіщо потрібні поля цього класу.

Перше – це name. Я волію називати події, щоб можна було побачити в логах, яка саме подія була викликана. Зазвичай як ім'я події я використовую ім'я класу разом з ім'ям методу, що викликається в ньому для викидання (raise). Це зручна практика, тому що дозволяє не нишпорити судомно по стеку в пошуках того, хто подію викинув, а просто в консолі налагодження подивитися це значення.

Методи addReceiver та removeRecevier логічні - вони приймають об'єкт і селектор, які надалі прийматимуть виклики.

Методи invoke повинні викидати подію, передаючи її для обробки підписаних об'єктів. Вонидаються у трьох варіантах - для того, щоб не передавати порожні значення nil у тому випадку, якщо в якихось параметрах події немає потреби.

Метод clearReceivers внутрішній, його краще визначати в анонімній секції, оскільки код, що викликає, не повинен мати можливості відписувати інші об'єкти від подій, але історично склалося так, що він винесений в інтерфейс. Це легко виправити, якщо вам здається неправильним.

Нарешті, властивість runLoop потрібна в тому випадку, якщо ви збираєтеся робити так, щоб деякі події були прив'язані до певного потоку (thread). Наприклад, це необхідно, якщо існує якийсь код у worker thread має оновлювати візуальну частину програми, або навпаки — з UI thread має бути доступ до якогось worker thread, який синхронізується через чергу повідомлень, тобто якщо є необхідність викидати події та обробляти їх у різних потоках.

Тепер визначимо пару допоміжних макросів, які дадуть нам можливість вбудовувати логіку роботи з подіями до класу буквально двома рядками.

Тепер для того, щоб створити в класі подію, потрібно визначити внутрішню перемінну змінну:

Визначити подію в інтерфейсі

І пов'язати список із подією

У класі автоматично додаються два методи - addEventHandler:action: і removeEventHandler:action: , а викликати подію можна через методи invoke об'єкта _handlers .

Звичайно, не варто забувати про те, що об'єкт _handlers потрібно ініціалізувати у конструкторі

І знищувати у деструкторі об'єкту

У другій частині статті я розповім, до яких проблем веде використання цього підходу і як справлятися з труднощами «мертвих» посилань, які виникають у будь-якому більш-менш об'ємному додатку в результаті наших.своїх помилок.

Хардкорна конфа за С++. Ми запрошуємо лише профі.