Как создать NSNumberFormatter Singleton в Swift (1.2 и 1.1) — дополнение к Заданию 2

Мне было необходимо использовать класс NSNumberFormatter для изменения вида представления числа в  моем  CalculatorBrain в Задании 2 стэнфордского курса «Разработка iOS 8 приложений на Swift. Winter 2015», например, с 2324.00 на 2 324 без десятичных знаков, то есть придать универсальному NSNumberFormatter некоторую нужную мне конфигурацию. Из-за того, что инициализация NSNumberFormatter может быть экстремально дорогой операцией в iOS, способной «поставить на колени» отдельные приложения, не хотелось бы, чтобы производительность приложения зависела от этого, особенно если используется одна и та же конфигурацию  NSNumberFormatter всюду в моем приложении. Будем использовать Singleton ( синглтон ) — ровно один экземпляр некоторого класса, глобально  доступный всем клиентам.

Singleton (синглтон) — это популярный паттерн (шаблон ) проектирования. Если вы новичок в Swift, вам будет интересно, как можно в Swift создать Singleton. В Objective-C, возможно, вы уже пробовали для создания синглтона dispatch_once из Grand Central Dispatch.

В настоящее время существует прекрасный пост в  stack overflow, в котором рассматриваются все пути создания Singleton в Swift, хотя этот пост не снабжен каким-то «глубоким» практическим примером. Класс  NSNumberFormatter широко используется и пример использования Singletons в классе  NSNumberFormatter будет очень полезен.

Давайте начнем создавать Singleton для NSNumberFormatter .Во-первых, нам нужно импортировать библиотеку Foundation

[objc]import Foundation
[/objc]

Затем, мы декларируем класс, называемый CalculatorFormatter, который будет наследовать от класса NSNumberFormatter.

[objc]class CalculatorFormatter: NSNumberFormatter {
}
[/objc]

Чтобы наследовать от класса NSNumberFormatter, необходимо реализовать required init функцию. Мы выполним это, просто вызвав ту же самую функцию для super класса,  и затем передадим параметр aDecoder в эту функцию. Это совершенно не влияет на решение нашей проблемы, но это обязательное требование.

[objc]required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}[/objc]

Переопределяем init()

Теперь мы можем переопределить (override) стандартную init функцию, в которой мы установим некоторые значения по умолчанию при инициализации. Внутри init функции, во-первых, необходимо вызвать super.init функцию. Вот код для нужного нам форматирования числа в CalculatorBrain (у вас он может быть другим).

[objc]override init() {
super.init()
self.locale = NSLocale.currentLocale()
self.maximumFractionDigits = 10
self.notNumberSymbol = "Error"
self.groupingSeparator = " "
self.numberStyle = .DecimalStyle
}
[/objc]

Далее рассмотрим два способа создания Singleton: один для версии Swift 1.2 и выше, второй — для версии Swift ниже 1.2. Первый способ опирается на создание константы ссылочного типа, к которому относится class. Такие константы разрешены в Swift 1.2.

 Singleton одной строкой в версии Swift 1.2 и выше

В Swift 1.2 или выше вам разрешено иметь в классах “static” методы и свойства (как алиасы для “class final”). Теперь вы можете декларировать хранимые  static свойства (static stored properties ) в классах, которые хранятся глобально и lazy инициализируются по первому обращению (как глобальные переменные). Это так называемые  “static” методы и свойства типа (это согласна терминологии Swift, а если по-старому, мы бы сказали класса). Свойства могут декларироваться как константы с помощью ключевого слова «let»  и как переменные с помощью ключевого слова «var«.

Для получения Singleton нам достаточно задекларировать static свойство типа как константу (type constant property), используя ключевые слова “static let”. Назовем нашу константу sharedInstance и  инициализируем ее как экземпляр нашего класса CalculatorFormatter .

[objc]static let sharedInstance = CalculatorFormatter()
[/objc]

Теперь наш Singleton готов — он был создан буквально одной строкой.

Получаем окончательный код для случая Swift 1.2 и выше, когда используется static константа типа .

[objc]import Foundation

class CalculatorFormatter: NSNumberFormatter {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

override init() {
super.init()
self.locale = NSLocale.currentLocale()
self.maximumFractionDigits = 10
self.notNumberSymbol = "Error"
self.groupingSeparator = " "
self.numberStyle = .DecimalStyle
}

// Swift 1.2 or above
static let sharedInstance = CalculatorFormatter()
}
[/objc]

На Playground проверим, что вызывая дважды CalculatorFormatter.sharedInstance, мы получаем один и тот же экземпляр Calcуlatorformatter. Далее  вызоваем метод stringFromNumber у синглтона sharedInstance и передаем ему число. Например, 20.00.  Используя оператор ??, получим представление «20» числа 20.00. Попробуем число 55550.00 и получим представление «55 550» числа 55550.00. Мы видим, что наш формат представления числа убирает нули и добавляет пробел в качестве разделителя групп цифр. Это то, что нам нужно.
Screen Shot 2015-04-17 at 8.48.26 PM

Singleton  в виде глобальной константы

Есть еще один путь — создание глобальной константы.  Apple  утверждает

Глобальные константы и переменные всегда являются отложенными (lazy) вычислениями, так же как и отложенные хранимые свойства (Lazy Stored Properties). В отличие от отложенных хранимых свойств, глобальные константы и переменные не нуждаются в маркировке lazy модификатором.

Кроме того, глобальные константы и переменные всегда используют «за сценой» ‘dispatch_once‘ . Поэтому можно использовать глобальную константу в качестве синглтона

[objc]
let formatter = CalculatorFormatter()
[/objc]

 Изменения в коде CalculatorBrain в Задании 2.

Добавляем в конец файла CalculatorBrain.swift класс CalculatorFormatter.
Screen Shot 2015-04-17 at 5.19.33 PM

Делаем изменения в CalculatorBrain.swift  и в ViewController.swift подобно тому, как показано ниже (используется 1-ый способ создания синглтона в Swift 1.2)

Screen Shot 2015-04-17 at 6.07.09 PM

Добавляем в файл CalculatorBrainTests.swift тесты для нашего Singleton, проверяющие, что это действительно один и тот же экземпляр класса CalculatorFormatter, независимо от того, в какой очереди он вызывается.

Screen Shot 2015-04-17 at 6.30.42 PM

Код для CalculatorBrain вместе с Playground можно найти на GitHub.

Код для случая использования глобальной константы в качестве Singleton можно найти на GitHub.

Использовались материалы

How to Create NSNumberFormatter Singleton in Swift.

NSFormatter

Swift Singleton

 

 

Как создать NSNumberFormatter Singleton в Swift (1.2 и 1.1) — дополнение к Заданию 2: 2 комментария

  1. Класс! На примере NumberFormatter в CalculatorBrain разобрался как это работает!! Очень красивое решение 🙂 Спасибо!

  2. Нашел вариант с использованием closure 🙂

    class CalculatorFormatter: NumberFormatter {
    static let sharedInstance: CalculatorFormatter = {
    var instance = CalculatorFormatter()
    // setup code
    instance.locale = .current
    instance.numberStyle = .decimal
    instance.maximumFractionDigits = 10
    instance.notANumberSymbol = «Error»
    instance.groupingSeparator = » »
    return instance
    }()
    }

    https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID14

Обсуждение закрыто.