Синглтон на Swift, или как правильно написать Singleton
Для написания Синглтона (Singleton) на языке Swift можно использовать несколько подходов. Теоретически — все будут работать, однако надо бы до конца разобраться какой же вариант является правильным с точки зрения работы и написания кода.
Для этого давайте разберем что делает Синглтон Синглтоном.
Я не буду вдаваться в подробности зачем вообще нужен Singleton и как его использовать, — это не является темой для этой статьи. А вот синтаксис написания — мы разберем подробно, от исторического определения на Objective-C до современного Swift 2.1
Есть по существу три параметра или свойства (кому как удобно), которыми должен обладать Singleton:
- Singleton должен быть уникальным. У Этого класса может быть только один экземпляр для всего жизненного цикла приложения, в котором он существует. Singleton создается для отслеживания какого либо глобального состояния, например для подсчета игровых очков в игре. Также хорошие и известные начинающим программистам примеры экземпляры классов NSNotificationCenter, UIApplication и NSUserDefaults.
- Из правила №1, следует для того, чтобы иметь только один экземпляр на протяжении всего жизненного цикла приложения, класс Singleton должен быть потоко-безопасным. При использовании мультизадачности (multithreading) и программировании в нескольких потоках, следует внимательно следить за созданием экземпляров класса, который будет Сингтоном. Проще говоря, если Singleton неправильно описан в коде, то может возникнуть ситуация с двумя параллельными потоками, которые пытаются инициализировать Singleton в одно и то же время. В результате потенциально могут получится два отдельных экземпляра Singleton. Т.е. мы должны обернуть инициализацию класса в
dispatch_once
блок от GCD, чтобы убедиться, код инициализации работает только один раз во время выполнения. - Для поддержания уникальности Singleton, инициализатор синглтона должен быть private. Это позволит предотвратить создание экземпляров вашего Singeton другими объектами.
Начнем издалека: давайте вспомним как инициализация Singleton выглядела при написании на языке программирования Objective-C
// //proSwift.ru // @interface OnlyOneST: NSObject @end @implementation OnlyOneST + (instancetype)sharedInstance { static OnlyOneST *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[OnlyOneST alloc] init]; }); return sharedInstance; } @end
Это все великолепно работает, но мы же собрались рассматривать реализацию на Swift! С самого начала могу сказать что Swift позволяет сделать четыре реализации нашей задачи, все четыре вариации я опишу в этой статье, и только последняя будет элегантной и полностью функциональной. Так что если вы не хотите читать как я растекаюсь мыслю по веб-странице можете прокрутить ее вниз и увидеть нужный кусок кода.
- Итак первый вариант — это запись кода на Objective-C языком Swift:
// //proSwift.ru // class OnlyOneST { class var sharedInstance: OnlyOneST { struct Static { static var onceToken: dispatch_once_t = 0 static var instance: OnlyOneST? = nil } dispatch_once (&Static.onceToken) { Static.instance = OnlyOneST( ) } return Static.instance! } }
Как было указано ранее, — этот вариант — это портирование кода из начала статьи.
2) Второй вариант — это запись с использованием статичной структуры.
// //proSwift.ru // class OnlyOneST { class var sharedInstance: OnlyOneST { struct Static { static let instance = OnlyOneST() } return Static.instance } }
Тут надо учитывать, что Swift 1.0, не поддерживал статические переменные класса. Однако, в структурах их можно было использовать! Из-за этих ограничений на статические переменные, программисты были вынуждены использовать модели подобные этой. Это лучше, чем прямой Objective-C порт, но все еще не достаточно хорошо.
3) Третий вариант — установка глобальной переменной
// //proSwift.ru // private let sharedSingleton = OnlyOneST() class TheOneAndOnlyKraken { class var sharedInstance: OnlyOneST { return sharedSingleton } }
В Swift 1.2, были добавлены спецификаторы контроля доступа и возможность использования статических членов класса. Это означало, что более нет необходимости использовать глобальную переменную, и соответственно иметь беспорядок в глобальном пространстве имен.
Сейчас на этом месте, внимательный читатель может спросить, почему мы не использовали dispatch_once
в наших реализациях структур или глобальных переменных? Ответ на этот вопрос нам дает Swift Блог Apple:
The lazy initializer for a global variable (also for static members of structs and enums) is run the first time that global is accessed, and is launched as `dispatch_once` to make sure that the initialization is atomic. This enables a cool way to use `dispatch_once` in your code: just declare a global variable with an initializer and mark it private.
То есть Apple утверждает, что если создать глобальную переменную и ее инициализатор сделать private, то за кулисами языка инициализация оборачивается в блок dispatch_once
при доступе в глобальное пространство.
Финальный рабочий вариант
Ну и в конце концов мы подошли к четвертому варианту определения Singleton, и это будет вариант описания одной строкой!
// //proSwift.ru // class OnlyOneST{ static let sharedInstance = OnlyOneST() }
Для доказательства того, что такая конструкция работает и работает верно можно собрать небольшой проект, и расставить точки останова в компиляторе. Следующую статью я посвящу именно этому.
Да! Язык Swift предоставляет весьма элегантные решения для тривиальных и не очень задач. Именно поэтому программисты которые решили освоить программирование iOS и Mac выбирают именно это язык программирования.
А что если в последнем варианте мы можем просто сделать
let anotherInstance =OnlyOneST()
и получается у нас 2 экземпляра класса OnlyOneST ?
Инициализацию Sigletone нужно производить
let istance = OnlyOneST().sharedInstance
и тогда
let anotherIstance = OnlyOneST().sharedInstance
будет ссылаться на тот же объект.
А чтобы быть правильным до конца, нужно инициализатор для класса пометить private:
private init() {
}
И тогда на строке
let anotherIstance = OnlyOneST()
компилятор выдаст ошибку.
У меня уже почти готова статья про Singlton как шаблон программирования. В ней будет приведен пример создания Singleton на Swift в стиле Apple, как, допустим, в NSUserDefaults или UIApplicaton. В ней будет описан метод более привычного использования этого шаблона.