Синглтон на 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 позволяет сделать четыре реализации нашей задачи, все четыре вариации я опишу в этой статье, и только последняя будет элегантной и полностью функциональной. Так что если вы не хотите читать как я растекаюсь мыслю по веб-странице можете прокрутить ее вниз и увидеть нужный кусок кода.

  1. Итак первый вариант — это запись кода на 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 выбирают именно это язык программирования.

2 Comments on “Синглтон на Swift, или как правильно написать Singleton

  1. А что если в последнем варианте мы можем просто сделать
    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. В ней будет описан метод более привычного использования этого шаблона.

Добавить комментарий для fmaxx Отменить ответ

Ваш адрес email не будет опубликован.

*

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.