Сінглтон (Переклад з англійської глави «Singleton» з книги «Pro Objective-C Design Patterns for
Клас синглтона в об'єктно-орієнтованому додатку завжди повертає той самий екземпляр себе. Він забезпечує глобальну точку доступу ресурсів, які надає об'єкт класу. Паттерн з такою функціональністю називається Сінглтон. У цьому розділі ми вивчимо можливості реалізації та використання патерна Сінглтон у Objective-C та фреймворку Cocoa Touch на iOS.
Що являє собою патерн Сінглтон?
Паттерн Сінглтон – чи не найпростіший із патернів. Його призначення в тому, щоб зробити об'єкт класу єдиним екземпляром у системі. Насамперед потрібно заборонити створювати більше одного екземпляра класу. Для цього можна використовувати фабричний метод (глава 4), який повинен бути статичним, тому що немає сенсу дозволяти екземпляру класу створювати інший єдиний екземпляр. Малюнок 7-1 показує структуру класу простого синглтону.

Статичний uniqueInstance – це єдиний екземпляр класу Singleton, представлений у вигляді змінної класу, яку статичний метод sharedInstance поверне клієнтам. Зазвичай sharedInstance перевірятиме, чи інстанційований uniqueInstance . Якщо ні, метод створить його перед поверненням.
Примітка.Паттерн Сінглтон:Перевіряє, що є тільки один екземпляр класу і забезпечує єдину точку доступу до нього. .
Коли можна використовувати патерн Сінглтон?
Є сенс подумати про використання патерну Сінглтон, якщо:
- У системі може бутитільки один екземпляр класу, який має бути доступний через добре відому точку доступу, наприклад, фабричний метод.
- Єдиний екземпляр може бути розширений тільки шляхом успадкування, і код клієнта не втратить працездатність від використання розширеного об'єкта.
Паттерн Сінглтон забезпечує звичний спосіб створення унікального екземпляра зі зручним способом доступу до ресурсу. Метод використання посилання на статичний глобальний об'єкт не запобігає створенню іншого екземпляра того самого класу. Підхід використання методу класу, хоч і може надати глобальну точку доступу, але немає гнучкості поділу коду. Статична глобальна змінна містить єдине посилання на екземпляр класу. Інший клас або метод, які мають до неї доступ, фактично поділяють ту саму копію з іншими класами або методами, що використовують ту саму змінну. Схоже на те, що нам потрібне. Все виглядає чудово, поки використовується та сама глобальна змінна по всьому додатку. Таким чином, фактично патерн Сінглтон не потрібен. Але зачекайте! Що, якщо хтось у вашій команді визначив у коді таку саму глобальну змінну, як ваша? Тоді буде дві копії одного і того ж глобального об'єкта в одному додатку – тому глобальна змінна насправді не вирішує проблеми.
Метод класу надає можливість поділу без створення об'єкта. Єдиний екземпляр ресурсу підтримується методом класу. Однак цей підхід має нестачу гнучкості у випадку, якщо клас вимагає успадкування для забезпечення більшої функціональності.
Клас Сінглтона може гарантувати єдину, узгоджену та добре відому точку доступу для створення та доступу до єдиного об'єкта класу.Паттерн забезпечує таку гнучкість, що з його підкласів може перевизначити метод створення екземпляра і мати повний контроль над створенням об'єкта класу без зміни коду клієнта. Ще краще те, що реалізація методу створення екземпляра може обробляти створення об'єкта динамічно. Реальний тип класу може визначатись під час виконання, щоб бути впевненим, що створюється коректний об'єкт. Ця техніка обговорюватиметься далі.
Існує також гнучка версія патерна Сінглтон, в якій фабричний метод завжди повертає той самий екземпляр, але можна на додаток алоціровать та ініціалізувати інші екземпляри. Ця менш строга версія патерну обговорюється в розділі «Використання класу NSFileManager» далі у цьому розділі.
Реалізація Сінглтона в Objective-C
Є щось, над чим варто подумати, щоб спроектувати клас Сінглтона правильно. Перше питання, яким потрібно поставитися, — як переконатися, що тільки один екземпляр класу може бути створений? Клієнти в додатку, написаному іншими об'єктно-орієнтованими мовами, такими як C++ і Java, не можуть створити об'єкт класу, якщо його конструктор оголошений закритим. А як справи в Objective-C?
Будь-який Objective-C метод є відкритим, і мова сама по собі динамічно типізована, тому будь-який клас може надіслати повідомлення іншому (виклик методу C++ і Java) без значних перевірок під час компіляції (тільки попередження компілятора, якщо метод не оголошений). Також фреймворк Cocoa (включно з Cocoa Touch) використовує управління пам'яттю методом підрахунку посилань для підтримки часу життя об'єкта в пам'яті. Всі ці особливості роблять реалізацію Сінглтона в Objective-C досить складною. В оригінальному прикладі книги «Паттерни проектування» прикладСінглтон на C++ виглядав, як показано в лістингу 7-1.
Лістинг 7-1. Вихідний приклад на C++ патерну Сінглтон із книги «Паттерни проектування».
Як описано в книзі, реалізація C++ проста і прямолінійна. У статичному методі Instance() статична змінна _instance перевіряється на 0 ( NULL ). Якщо так, то створюється новий об'єкт класу Singleton, а потім повертається. Хтось із вас може подумати, що Objective-C версія не сильно відрізняється від свого побратима і має виглядати, як у лістингах 7-2 та 7-3.
Лістинг 7-2. Оголошення класу Singleton у Singleton.h
Лістинг 7-3. Реалізація методу sharedInstance Singleton.m
Якщо так, то це дуже простий розділ, і ви вже вивчили один патерн, реалізований на Objective-C. Насправді є кілька труднощів, які потрібно подолати, щоб зробити реалізацію достатньо надійною для використання у реальному додатку. Якщо потрібна «строга» версія патерну Сінглтон, то є дві головні проблеми, які потрібно вирішити, щоб його можна було використати в реальному коді:
- Об'єкт, що викликає, не може створити об'єкт Сінглтона через інші засоби алокації. Інакше стане можливим створення безлічі екземплярів класу Сінглтона.
- Обмеження на створення об'єкта Сінглтон мають бути узгоджені з моделлю підрахунку посилань.
Лістинг 7-4 показує реалізацію, яка близька до тієї, якої ми прагнемо. Код досить довгий, тому розіб'ємо його на кілька частин для зручності обговорення.
Лістинг 7-4. Більше відповідна реалізація Singleton в Objective-C
Всередині методу sharedInstance , так само, як і в першому прикладі, спочатку перевіряється, що єдиний екземпляр класу створений, інакше створюється новий і повертається. Але на цей раз вінвикликає [[super allocWithZone:NULL] init] для створення нового екземпляра замість використання інших методів, таких як alloc . Чому super, а не self? Це тому, що базові методи виділення пам'яті під об'єкт у нашому класі перевизначені, тому потрібно «позичити» цю функціональність у базового класу, в даному випадку NSObject , щоб допомогти зробити низькорівневу рутинну роботу з виділення пам'яті для нас.
Є кілька методів, які відносяться до управління пам'яттю в класі Singleton, про які потрібно подумати. У методі allocWithZone:(NSZone *)zone відбувається просто повернення екземпляра класу, який повертається з методу sharedInstance . У фрейворку Cocoa Touch при виклику методу класу allocWithZone:(NSZone *)zone пам'ять під екземпляр буде виділена, його лічильник посилань буде встановлений в 1, і екземпляр буде повернуто. Ми бачили, що метод alloc використовується у багатьох ситуаціях; фактично alloc викликає allocWithZone: із зоною, встановленою в NULL , щоб виділити пам'ять під екземпляр у зони за замовчуванням. Деталі створення об'єкта та управління пам'яттю знаходяться за межами цієї книги. Ви можете звернутися до документації для подальших роз'яснень.
Інші методи, такі як retain , release і autorelease , перевизначаються, щоб переконатися, що вони не зроблять нічого (в моделі управління пам'яттю з підрахунком посилань), крім як повернуть self . Метод retainCount повертає NSUIntegerMax (4,294,967,295) для запобігання видаленню екземпляра з пам'яті протягом життя програми.
Навіщо викликати retain Сінглтону?Можливо, ви помітили, що ми викликаємо retain об'єкту Сінглтона, який повертається з методу sharedInstance в allocWithZone: , але retain перевизначається і фактично ігнорується в нашій реалізації. Враховуючи це, унас буде можливість зробити клас Сінглтона менш «суворим» (тобто, можна буде виділяти пам'ять під додаткові екземпляри та ініціалізувати їх, але фабричний метод sharedInstance завжди повертає той самий екземпляр або об'єкт Сінглтона стає руйнованим). Підкласи можуть перевизначати методи retain, release та autorelease знову, щоб забезпечити відповідну реалізацію управління пам'яттю. Гнучка версія патерна Сінглтон обговорюється в розділі "Використання класу NSFileManager" далі в цьому розділі.
Ми вже досить докладно розглянули, як має виглядати патерн Сінглтон у Objective-C. Однак є ще дещо, про що варто добре подумати, перш ніж можна буде його використати. Що, якщо ми хочемо успадкувати від початкового класу Singleton? Розглянемо як це зробити.
Спадкування від Сінглтона
Виклик alloc перенаправляється в super , тому клас NSObject піклується виділення пам'яті під об'єкт. Якщо ми успадковуємося від класу Singleton без модифікацій, то екземпляр, що повертається, завжди буде типу Singleton . Оскільки клас Singleton перевизначає всі методи, що відносяться до створення екземпляра, досить непросто успадковувати від нього. Але нам пощастило; можна використовувати деякі функції Foundation для інстанція будь-якого об'єкта, виходячи з його типі класу. Одна з них – це id NSAllocateObject (Class aClass, NSUInteger extraBytes, NSZone * zone) . Тому, якщо ми хочемо інстанцувати об'єкт класу, що називається Singleton, можна зробити наступне:
Перший параметр – це тип класу Singleton. Другий параметр призначений для будь-якої кількості додаткових байт для індексованих змінних екземпляра, який завжди дорівнює 0. Третій параметр - позначення зони пам'яті, що виділяється; майже завждивикористовується зона за замовчуванням (параметр дорівнює NULL). Тому можна інстанціювати будь-які об'єкти, використовуючи цю функцію, знаючи тип класу. Що потрібно робити для успадкування від класу Singleton? Давайте пригадаємо, що початковий варіант методу sharedInstance виглядає так:
Якщо використовувати трюк з NSAllocateObject для створення екземпляра, то він стане таким:
Тепер неважливо, чи інстанцуємо ми клас Singleton або якийсь із його підкласів, ця версія зробить все коректно.
Потокобезпека
Клас Singleton у прикладі гарний лише загального використання. Якщо потрібно використовувати об'єкт синглтона в багатопотоковому середовищі, необхідно зробити його потокобезопасным. Для цього можна вставити блоки @synchronized() або використовувати екземпляри NSLock навколо перевірки на nil для статичної змінної екземпляра sharedSingleton_ . Якщо є якісь інші властивості, які теж потрібно захистити, то можна зробити їх atomic.
Використання Сінглтонів у фреймворку Cocoa Touch
У процесі знайомства з документацією Cocoa Touch Framework вам зустрінеться багато різних класів синглтонів. Ми поговоримо про деякі з них у цьому розділі - UIApplication, UIAccelerometer і NSFileManager.
Використання класу UIApplication
Одним із найчастіше використовуваних синглтон класів у фреймворку є клас UIApplication. Він забезпечує централізовану точку керування та координування для iOS-додатків.
Кожна програма має єдиний екземпляр UIApplication. Він створюється як синглтон об'єкт функцією UIApplicationMain при запуску програми і доступний під час виконання через метод класу sharedApplication .
Об'єкт UIApplication виконує багато службових завдань для програми, включаючи початковумаршрутизацію вхідних повідомлень користувача, а також диспетчеризацію повідомлень про дії для об'єктів класу UIControl до відповідних цільових об'єктів. Він підтримує список всіх відкритих об'єктів UIWindow. Об'єкт програми пов'язаний з об'єктом делегата програми UIApplicationDelegate , який інформується про будь-які значущі події під час виконання програми, таких як запуск, попередження про брак пам'яті, завершення програми та виконання фонових процесів. Обробники цих подій дають можливість делегату налаштувати поведінку програми.
Використання класу UIAccelerometer
Використання класу NSFileManager
Клас NSFileManager колись був «суворою» реалізацією патерна Сінглтон перед Mac OS X 10.5 та в iOS 2.0. Виклик його методу init нічого не робить, і його єдиний екземпляр може бути створений і доступний лише через метод класу defaultManager . Однак оскільки синглтон реалізація не є потокобезпечним, то доводиться створювати нові екземпляри NSFileManager , щоб забезпечити цю безпеку. Цей підхід розглядається як гнучкіша реалізація Сінглтона, в якій фабричний метод завжди повертає той самий екземпляр, але можна виділити та ініціалізувати також додаткові екземпляри.
Якщо потрібно реалізувати "суворий" синглтон, необхідна реалізація, схожа на приклад, описаний у попередніх розділах. Інакше – не перевизначайте allocWithZone: та інші пов'язані методи.
Паттерн Сінглтон – один із найбільш широко використовуваних патернів у майже будь-якому типі програми, і не лише під iOS. Сінглтон може бути корисним у тому випадку, коли потрібний централізований клас для координування сервісів програми. Цей розділ відзначає кінець цієї частини про створення об'єктів. У наступній частиніми побачимо деякі патерни проектування, які фокусуються на адаптації/консолідації об'єктів із різними інтерфейсами.