Я почув і забув.
Я записав і запам'ятав.
Я зробив і зрозумів.
Я навчив іншого, тепер я майстер.
(В. В. Бублик)

Небольшое вступление.
Я не зря вынес в начало поста цитату на украинском языке. Дело в том, что именно эти слова я услышал от своего преподавателя программирования на втором курсе университета, и именно в таком виде я вспоминаю эти слова до сих пор. Как вы можете догадаться, эта цитата является отсылкой к высказыванию Конфуция, но в ней есть очень важное дополнение о достижении мастерства.
И именно эти слова и сподвигли меня на написание данной серии постов. Дело в том, что я — начинающий iOS разработчик, и я очень хочу разобраться в паттернах проектирования. И я не придумал лучшего способа, чем взять книгу "Паттерны проектирования" Эрика и Элизабет Фримен, и написать примеры каждого паттерна на Objective-C и Swift. Таким образом я смогу лучше понять суть каждого паттерна, а также особенности обоих языков.
Содержание:
Часть 0. Синглтон-Одиночка
Часть 1. Стратегия
Часть 2. Наблюдатель
Итак, начнем с самого простого на мой взгляд паттерна.
Одиночка, он же — синглтон.
Основная задача синглтона — предоставить пользователю один и только один объект определенного класса на весь жизненный цикл приложения. В iOS-разработке, как по мне — самый лучший пример необходимости такого объекта — класс UIApplication. Вполне логично, что в течение жизни нашего приложения у нас должен быть один-единственный объект класса UIApplication.
Итак, разберемся что такое синглтон в Objective-C и Swift на примерах из книги.
Давайте сначала узнаем как вообще создать объект какого-нибудь класса. Очень просто:
// Objective-C [[MyClass alloc] init]
// Swift MyClass()
И тут авторы подводят нас к мысли, что приведенным выше способом можно создать сколько угодно объектов этого класса. Таким образом, первое что нужно сделать на пути к синглтону — запретить создание объектов нашего класса извне. В этом нам поможет приватный инициализатор.
И если в swift это реализуется тривиально:
// Swift class MyClass { private init() {} }
То в objective-c все не так просто на первый взгляд. Дело в том, что все классы obj-c имеют одного общего предка: NSObject, в котором есть общедоступный инициализатор. Поэтому в файле заголовка нашего класса нужно указать на недоступность этого метода для нашего класса:
// Objective-C @interface MyClass : NSObject - (instancetype)init UNAVAILABLE_ATTRIBUTE; @end
Таким образом попытка создать объект нашего класса извне вызовет ошибку на этапе компиляции. Окей. Теперь и в objective-c у нас есть запрет на создание объектов нашего класса. Правда это еще не совсем приватный инициализатор, но мы к этому вернемся через пару секунд.
Итак, по сути мы получили класс, объекты которого не могут создаваться, потому что конструктор — приватный. И что со всем этим делать? Будем создавать объект нашего класса внутри нашего же класса. И будем использовать для этого статический метод (метод класса, а не объекта):
// Swift class MyClass { private init() {} static func shared() -> MyClass { return MyClass() } }
// Objective-C @implementation MyClass + (instancetype)sharedInstance { return [[MyClass alloc] init]; } @end
И если для swift опять все просто и понятно, то с objective-c возникает проблема с инициализацией:

Вполне логично, ведь мы сказали ранее, что - (instancetype)init недоступен. И он недоступен в том числе и внутри нашего класса. Что делать? Написать свой приватный инициализатор в файле реализации и использовать его в статическом методе:
// Objective-C @implementation MyClass - (instancetype)initPrivate { self = [super init]; return self; } + (instancetype)sharedInstance { return [[MyClass alloc] initPrivate]; } @end
(да, и не забудьте вынести метод + (instancetype)sharedInstance в файл заголовка, он должен быть публичным)
Теперь все компилируется и мы можем получать объекты нашего класса таким способом:
// Objective-C [MyClass sharedInstance]
// Swift MyClass.shared()
Наш синглтон почти готов. Осталось только исправить статический метод так, чтобы объект создавался только один раз:
// Objective-C @implementation Singleton - (instancetype)initPrivate { self = [super init]; return self; } + (instancetype)sharedInstance { static Singleton *uniqueInstance = nil; if (nil == uniqueInstance) { uniqueInstance = [[Singleton alloc] initPrivate]; } return uniqueInstance; } @end
// Swift class Singleton { private static var uniqueInstance: Singleton? private init() {} static func shared() -> Singleton { if uniqueInstance == nil { uniqueInstance = Singleton() } return uniqueInstance! } }
Как видите, для этого нам понадобилась статическая переменная, в которой и будет храниться единожды созданный объект нашего класса. Каждый раз при вызове нашего статического метода она проверяется на nil и, если объект уже создан и записан в эту переменную — он не создается заново. Наш синглтон готов, ура! :)
Теперь немного примеров из жизни из книги.
Итак, у нас есть шоколадная фабрика и для приготовления мы используем высокотехнологичный нагреватель шоколада с молоком (я просто обожаю молочный шоколад), который будет управляться нашим программным кодом:
// Objective-C // файл заголовка ChocolateBoiler.h @interface ChocolateBoiler : NSObject - (void)fill; - (void)drain; - (void)boil; - (BOOL)isEmpty; - (BOOL)isBoiled; @end // файл реализации ChocolateBoiler.m @interface ChocolateBoiler () @property (assign, nonatomic) BOOL empty; @property (assign, nonatomic) BOOL boiled; @end @implementation ChocolateBoiler - (instancetype)init { self = [super init]; if (self) { self.empty = YES; self.boiled = NO; } return self; } - (void)fill { if ([self isEmpty]) { // fill boiler with milk and chocolate self.empty = NO; self.boiled = NO; } } - (void)drain { if (![self isEmpty] && [self isBoiled]) { // drain out boiled milk and chocolate self.empty = YES; } } - (void)boil { if (![self isEmpty] && ![self isBoiled]) { // boil milk and chocolate self.boiled = YES; } } - (BOOL)isEmpty { return self.empty; } - (BOOL)isBoiled { return self.boiled; } @end
// Swift class ChocolateBoiler { private var empty: Bool private var boiled: Bool init() { self.empty = true self.boiled = false } func fill() { if isEmpty() { // fill boiler with milk and chocolate self.empty = false self.boiled = false } } func drain() { if !isEmpty() && isBoiled() { // drain out boiled milk and chocolate self.empty = true } } func boil() { if !isEmpty() && !isBoiled() { // boil milk and chocolate self.boiled = true } } func isEmpty() -> Bool { return empty } func isBoiled() -> Bool { return boiled } }
Как видите — нагреватель сначала заполняется смесью (fill), затем доводит ее до кипения (boil), и после — передает ее на изготовление молочных шоколадок (drain). Для избежания проблем нам нужно быть уверенными, что в нашей программе присутствует только один экземпляр нашего класса, который управляет нашим нагревателем, поэтому внесем изменения в программный код:
// Objective-C @implementation ChocolateBoiler - (instancetype)initPrivate { self = [super init]; if (self) { self.empty = YES; self.boiled = NO; } return self; } + (instancetype)sharedInstance { static ChocolateBoiler *uniqueInstance = nil; if (nil == uniqueInstance) { uniqueInstance = [[ChocolateBoiler alloc] initPrivate]; } return uniqueInstance; } // other methods @end
// Swift class ChocolateBoiler { private var empty: Bool private var boiled: Bool private static var uniqueInstance: ChocolateBoiler? private init() { self.empty = true self.boiled = false } static func shared() -> ChocolateBoiler { if uniqueInstance == nil { uniqueInstance = ChocolateBoiler() } return uniqueInstance! } // other methods }
Итак, все отлично. Мы на 100% уверены (точно на 100%?), что у нас есть только один объект нашего класса и никаких непредвиденных ситуаций на фабрике не произойдет. И если наш код на objective-c выглядит довольно неплохо, то swift выглядит недостаточно swifty. Попробуем его немного переписать:
// Swift class ChocolateBoiler { private var empty: Bool private var boiled: Bool static let shared = ChocolateBoiler() private init() { self.empty = true self.boiled = false } // other methods }
Дело в том, что мы можем спокойно хранить наш объект-одиночку в статической константе shared и нам совсем необязательно писать для этого целый метод с проверками на nil. Сам объект будет создан при первом обращении к этой константе и записан в нее один-единственный раз.
А как же многопоточность?
Все будет работать хорошо ровно до того момента, как мы захотим применить в нашей программе работу с потоками. Как же сделать наш синглтон потокобезопасным?
И опять же: в swift, как оказывается, совершенно не нужно выполнять каких-либо дополнительных действий. Константа уже потокобезопасна, ведь значение в нее может быть записано только один раз и это сделает тот поток, который доберется до нее первым.
А вот в objective-c необходимо внести коррективы в наш статический метод:
// Objective-C + (instancetype)sharedInstance { static ChocolateBoiler *uniqueInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ uniqueInstance = [[ChocolateBoiler alloc] initPrivate]; }); return uniqueInstance; }
Блок внутри dispatch_once гарантированно выполнится только один раз, когда самый первый поток до него доберется, все остальные потоки — будут ждать, когда закончится выполнение блока.
Итоги подведем.
Итак, мы разобрались как правильно писать синглтоны на objective-c и swift. Приведу вам итоговый код класса Singleton на обоих языках:
// Objective-C // файл заголовка Singleton.h @interface Singleton : NSObject - (instancetype)init UNAVAILABLE_ATTRIBUTE; + (instancetype)sharedInstance; @end // файл реализации Singleton.m @implementation Singleton - (instancetype)initPrivate { self = [super init]; return self; } + (instancetype)sharedInstance { static Singleton *uniqueInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ uniqueInstance = [[Singleton alloc] initPrivate]; }); return uniqueInstance; } @end
// Swift class Singleton { static let shared = Singleton() private init() {} }
П.С.
Хочу попросить всех читателей и комментаторов: если вы увидели какую-нибудь неточность или знаете как какой-либо из приведенных мною кусков кода написать правильнее/красивее/корректнее — скажите об этом. Я пишу здесь совсем не для того, чтоб учить других, а для того, чтоб научиться самому. Ведь пока мы учимся — мы остаемся молодыми.
Спасибо вам за внимание.
