Огляд property та KVO у objective c

objective
Привіт. Після недавнього переходу з objective c на java виявив відсутність у java таких хороших речей як властивостей і KVO(Key-Value Observing). У цій статті розповідається про те, навіщо в принципі ускладнювати доступ до внутрішніх змінних об'єктів, як це реалізовано objective c і яку користь з точки зору стеження за станом об'єктів ми отримуємо, при використанні властивостей і KVO.

Частина 1 - property

Властивості в objective c це грубо публічні змінні класу з модифікованими гетерами/сетерами. Для того, щоб краще зрозуміти, навіщо це потрібно подивимося на приклад. Допустимо у нас є клас company, що зберігає в собі число працівників даної компанії (people):

Скоротливим поглядом помічаємо потенційне джерело помилок — кількість людей у ​​компанії оголошено як int включає і негативні значення. В даному випадку проблему можна усунути заміною типу змінної на беззнакову, але що робити, якщо ми хочемо ще більше звузити діапазон допустимих значень. Скажімо відповідно до ТЗ кількість працівників у компанії не може бути більшою за тисячу. Типове вирішення цієї проблеми в мовах позбавлених вбудованої підтримки властивостей це створення пари акцесор/мутатор (accessor/mutator):

Мутатор дозволяє змінювати значення змінної з урахуванням обмежень, а наявність акцесора диктується необхідністю приховати внутрішню змінну. Інакше будь-хто міг би звернутися до змінної безпосередньо та записати туди некоректний результат. Здавалося б проблему вирішено, але варто відзначити один факт - використання пар акцесору/мутатора сильно ускладнює читання коду. Порівняйте код збільшення числа робітників на одиницю:

Очевидно, що в другому випадку код більш читаємо, та й змушувати програміста писати для кожної змінної париакцесор/мутатор не добре. Мова objective c дозволяє уникнути написання цього нудного коду за допомогою використання властивостей. Типовий опис класу з використанням властивостей має такий вигляд:

А в реалізації класу достатньо дописати один рядок, а інше компілятор зробить сам.

Після цього ми можемо звертатися до нашої змінної через звичну точку, але при використанні властивостей можна вказати низку додаткових параметрів:

Як видно, число опцій досить велике для задоволення типових потреб при написанні коду. У разі наявності обмежень на значення, що надсилаються, необхідно вручну перевизначити метод set. Також можна вручну перевизначити метод get (що в ряді випадків дозволяє позбутися зайвих змінних). Тільки варто пам'ятати, що в objective c функція акцесору пишеться без префікса get, а synthesize дозволяє вказати, як звертатися до змінної всередині класу. Приклад коду.

Вигода від використання властивостей очевидна. Ми можемо не тільки позбутися написання зайвого коду, але й створювати псевдозмінні, не розкриваючи, як вони по-справжньому влаштовані.

Крім спрощення синтаксису використання властивостей дозволяє використовувати ще кілька можливостей мови, а саме — звернення до змінної через її назву.

Або навіть записати туди нове значення. А враховуючи можливість отримання списку всіх властивостей прямо під час виконання програми, це відкриває великі можливості. Зауваження на мою особисту думку подібною можливістю краще не користуватися, бо це швидше за брудних хак, ніж типовий спосіб звернення до змінних. Докладніше про objective c runtime можна прочитати, наприклад, тут. Перед тим як розповідати про KVO варто згадати і про повідомлення в повідомленнях(notification)

Частина 2 -notification

Objective c дозволяє швидко та з мінімальними зусиллями реалізувати паттерн спостерігач. У двох словах паттерн спостерігач це шаблон проектування, коли є два об'єкта — спостерігач і наблюдаемый. Спостерігач цікавляться подіями, що відбуваються з спостерігається, а ось другий спостерігається флегматично тече за течією, зрідка посилаючи повідомлення про події, що відбуваються з ним. Спостережений навіть не цікавиться, а чи чує його хтось, але продовжує розсилати повідомлення. Подібний підхід дозволяє реалізовувати зворотний зв'язок до змін, що спостерігаються. Objective c має дуже просунутий механізм роботи з повідомленнями, так щоб зареєструвати спостерігача достатньо одного рядка коду:

Де object — об'єкт, який буде спостерігачем, складна конструкція @selector(observeNotification:) вказує який метод треба викликати у спостерігача (в даному випадку observeNotification:) і nameNotification — назва події за якою ми спостерігатимемо. В останньому параметрі можна вказати від якого об'єкта ми очікуємо отримати повідомлення, або ж просто отримувати всі повідомлення із заданим ім'ям, що зручно якщо заздалегідь невідомо, хто саме надішле повідомлення. У класі спостерігача необхідно реалізувати вказаний вище метод (observeNotification:), а в об'єкті який надсилає повідомлення достатньо написати:

І на цьому необхідний для найпростішої реалізації паттерна спостерігач код закінчується. Як видно нам не довелося писати будь-якого коду, який би зберігав зв'язки між спостерігачем і спостерігається, але поки була необхідність зміни коду спостерігається. А ось KVO дозволяє уникнути зайвого втручання у чужий код.

Частина 3 - KVO

KVO (key-value observing) - ще одна реалізація паттерна спостерігач,навіть цікавіша ніж попередня. Якщо в минулому варіанті спостерігач стежив за абстрактними подіями, знаючи лише ім'я, то в KVO область застосування дещо звужується. У цьому випадку спостерігач чітко знає об'єкт за яким спостерігає і за якою властивістю (property) у об'єкта він стежить. Коли значення цієї властивості змінюється, спостерігачеві надходить повідомлення і він відповідним чином реагують. У порівнянні з багатьма іншими мовами реалізація KVO в objective c радують досить простим синтаксисом. Так у коді спостерігача достатньо написати:

і кожного разу коли в company_a буде змінюватися значення змінної people спостерігач буде повідомлятися за допомогою виклику методу observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context і треба лише реалізувати код , який буде реагувати на повідомлення. Ви напевно хотіли запитати, а що треба дописати в коді класу об'єкта за яким ведеться спостереження (в даному випадку company) для реалізації подібної чудової можливості - так якщо використовувати властивості з автоматично синтезованими директивою @ synthesize методами get/set, то нічого дописувати не треба. В іншому випадку код змінює внутрішню змінну необхідно оточити двома допоміжними функціями (willChangeValueForKey: і didChangeValueForKey:). Так раніше використаний приклад варто доопрацювати наступним чином:

  • Мінімалізм коду. (досить написати лише кілька рядків, щоб повністю реалізувати патерн спостерігач)
  • Можливість стеження будь-якими властивостями(property) будь-яких класів як написаними нами, і чужими. Фактично зовнішні змінні завжди оформляються через властивості, що дозволяє легко стежити будь-якими змінами.
  • Повторююсь можливість стеженням за будь-якими властивостями будь-яких класів чи то state у NSOperation чи frame у UIView. KVO дозволяє стежити за зміною параметрів будь-якого класу
  • І ще раз — не важливо, хто писав клас, чи є його вихідний код чи ні. Ви матимете можливість стежити за змінами і це просто фантастична річ, яка на порядок спрощує написання коду реакцій на події.

Фактично вище за три рази згадано одну й ту саму гідність, але це тільки тому, що аналогів такого потужного механізму в інших популярних мовах немає. Якщо ви хочете відреагувати будь-яким чином на зміну одного з параметрів, вам необхідно або влазити всередину і переписувати код об'єкта (що в більшості випадків просто не можливо) або винаходити інші ще більш складні обхідні шляхи. KVO дозволяє вирішити цю проблему швидко і з мінімальним кількість праці, що робить KVO однією з головної фішок objective C.