Паттерны проектирования, взгляд iOS разработчика. Часть 2. Наблюдатель

Содержание:

Часть 0. Синглтон-Одиночка
Часть 1. Стратегия
Часть 2. Наблюдатель

Сегодня мы разберемся с «начинкой» паттерна «Наблюдатель». Сразу оговорюсь, что в мире iOS у вас не будет острой необходимости реализовывать этот паттерн, поскольку в SDK уже есть NotificationCenter. Но в образовательных целях мы полностью разберем анатомию и применение этого паттерна. К тому же, самостоятельная реализация может обладать большей гибкостью и, в некоторых случаях, быть более полезной.

«Кажется дождь собирается» (с)

Авторы книги «Паттерны проектирования» (Эрик и Элизабет Фримен), в качестве примера, предлагают применять паттерн «Наблюдатель» к разработке приложения Weather Station. Представьте, что у нас есть: метеостанция, и объект WeatherData, который обрабатывает данные от ее датчиков и передает их нам. Приложение же состоит из трех экранов: экрана текущего состояния погоды, экрана статистики и экрана прогноза.

Мы знаем, что WeatherData предоставляет нам такой интерфейс:

// Objective-C
- (double)getTemperature;
- (double)getHumidity;
- (double)getPressure;
- (void)measurementsChanged;
// Swift
func getTemperature() -> Double
func getHumidity() -> Double
func getPressure() -> Double
func measurementsChanged()

Также разработчики WeatherData сообщили, что при каждом обновлении погодных датчиков будет вызван метод measurementsChanged.

Конечно же, самое простое решение — написать код непосредственно в этом методе:

// Objective-C
- (void)measurementsChanged {
    double temp = [self getTemperature];
    double humidity = [self getHumidity];
    double pressure = [self getPressure];

    [currentConditionsDisplay updateWithTemp:temp humidity:humidity andPressure:pressure];
    [statisticsDisplay updateWithTemp:temp humidity:humidity andPressure:pressure];
    [forecastDisplay updateWithTemp:temp humidity:humidity andPressure:pressure];
}
// Swift
func measurementsChanged() {
    let temp = self.getTemperature()
    let humidity = self.getHumidity()
    let pressure = self.getPressure()

    currentConditionsDisplay.update(with: temp, humidity: humidity, and: pressure)
    statisticsDisplay.update(with: temp, humidity: humidity, and: pressure)
    forecastDisplay.update(with: temp, humidity: humidity, and: pressure)
}

Такой подход конечно же плох, потому что:
— программируем на уровне конкретных реализаций;
— сложная расширяемость в будущем;
— нельзя в рантайме добавлять/убирать экраны, на которых будет показана информация;
— … (свой вариант);

Поэтому паттерн «Наблюдатель» будет в этой ситуации очень кстати. Поговорим немного о характеристиках этого паттерна.

«Наблюдатель». Что под капотом?

Основные характеристики этого паттерна — наличие СУБЪЕКТА и, собственно, НАБЛЮДАТЕЛЕЙ. Связь, как вы уже догадались, один ко многим, и при изменении состояния СУБЪЕКТА происходит оповещение его НАБЛЮДАТЕЛЕЙ. На первый взгляд все просто.

Первое что нам понадобится — интерфейсы (протоколы) для наблюдателей и субъекта:

// Objective-C
@protocol Observer <NSObject>

- (void)updateWithTemperature:(double)temperature
                     humidity:(double)humidity
                  andPressure:(double)pressure;

@end

@protocol Subject <NSObject>

- (void)registerObserver:(id<Observer>)observer;
- (void)removeObserver:(id<Observer>)observer;
- (void)notifyObservers;

@end
// Swift
protocol Observer: class {
    func update(with temperature: Double, humidity: Double, and pressure: Double)
}

protocol Subject: class {
    func register(observer: Observer)
    func remove(observer: Observer)
    func notifyObservers()
}

Теперь нужно привести в порядок WeatherData (подписать на соотв. протокол и не только):

// Objective-C

// файл заголовка WeatherData.h
@interface WeatherData : NSObject <Subject>

- (void)measurementsChanged;
- (void)setMeasurementWithTemperature:(double)temperature
                             humidity:(double)humidity
                          andPressure:(double)pressure; // test method

@end

// файл реализации WeatherData.m
@interface WeatherData()

@property (strong, nonatomic) NSMutableArray<Observer> *observers;
@property (assign, nonatomic) double temperature;
@property (assign, nonatomic) double humidity;
@property (assign, nonatomic) double pressure;

@end

@implementation WeatherData

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.observers = [[NSMutableArray<Observer> alloc] init];
    }
    return self;
}

- (void)registerObserver:(id<Observer>)observer {
    [self.observers addObject:observer];
}

- (void)removeObserver:(id<Observer>)observer {
    [self.observers removeObject:observer];
}

- (void)notifyObservers {
    for (id<Observer> observer in self.observers) {
        [observer updateWithTemperature:self.temperature
                               humidity:self.humidity
                            andPressure:self.pressure];
    }
}

- (void)measurementsChanged {
    [self notifyObservers];
}

- (void)setMeasurementWithTemperature:(double)temperature
                             humidity:(double)humidity
                          andPressure:(double)pressure {

    self.temperature = temperature;
    self.humidity = humidity;
    self.pressure = pressure;
    [self measurementsChanged];
}

@end
// Swift
class WeatherData: Subject {

    private var observers: [Observer]
    private var temperature: Double!
    private var humidity: Double!
    private var pressure: Double!

    init() {
        self.observers = [Observer]()
    }

    func register(observer: Observer) {
        self.observers.append(observer)
    }

    func remove(observer: Observer) {
        self.observers = self.observers.filter { $0 !== observer }
    }

    func notifyObservers() {
        for observer in self.observers {
            observer.update(with: self.temperature, humidity: self.humidity, and: self.pressure)
        }
    }

    func measurementsChanged() {
        self.notifyObservers()
    }

    func setMeasurement(with temperature: Double,
                        humidity: Double,
                        and pressure: Double) { // test method

        self.temperature = temperature
        self.humidity = humidity
        self.pressure = pressure
        self.measurementsChanged()
    }

}

Мы добавили тестовый метод setMeasurement для имитации изменения состояний датчиков.

Поскольку методы register и remove у нас редко будут меняться от субъекта к субъекту, было бы хорошо иметь их реализацию по умолчанию. В Objective-C для этого нам понадобится дополнительный класс. Но для начала переименуем наш протокол и уберем из него эти методы:

// Objective-C
@protocol SubjectProtocol <NSObject>

- (void)notifyObservers;

@end

Теперь добавим класс Subject:

// Objective-C

// файл заголовка Subject.h
@interface Subject : NSObject

@property (strong, nonatomic) NSMutableArray<Observer> *observers;

- (void)registerObserver:(id<Observer>)observer;
- (void)removeObserver:(id<Observer>)observer;

@end

// файл реализации Subject.m
@implementation Subject

- (void)registerObserver:(id<Observer>)observer {
    [self.observers addObject:observer];
}

- (void)removeObserver:(id<Observer>)observer {
    [self.observers removeObject:observer];
}

@end

Как видите, в этом классе два метода и массив наших наблюдателей. Теперь в классе WeatherData убираем этот массив из свойств и унаследуемся от Subject, а не от NSObject:

// Objective-C
@interface WeatherData : Subject <SubjectProtocol>

В свифте, благодаря расширениям протоколов, дополнительный класс не понадобится.
Мы просто включим в протокол Subject свойство observers:

// Swift
protocol Subject: class {
    var observers: [Observer] { get set }

    func register(observer: Observer)
    func remove(observer: Observer)
    func notifyObservers()
}

А в расширении протокола напишем реализацию методов register и remove по умолчанию:

// Swift
extension Subject {

    func register(observer: Observer) {
        self.observers.append(observer)
    }

    func remove(observer: Observer) {
        self.observers = self.observers.filter {$0 !== observer }
    }

}

Принимаем сигналы

Теперь нам нужно реализовать экраны нашего приложения. Мы реализуем только один из них: CurrentConditionsDisplay. Реализация остальных аналогична.

Итак, создаем класс CurrentConditionsDisplay, добавляем в него два свойства и метод display (этот экран должен показывать текущее состояние погоды, как мы помним):

// Objective-C
@interface CurrentConditionsDisplay()

@property (assign, nonatomic) double temperature;
@property (assign, nonatomic) double humidity;

@end

@implementation CurrentConditionsDisplay

- (void)display {
    NSLog(@"Current conditions: %f degrees and %f humidity", self.temperature, self.humidity);
}

@end
// Swift
private var temperature: Double!
private var humidity: Double!

func display() {
    print("Current conditions: \(self.temperature) degrees and \(self.humidity) humidity")
}

Теперь нам нужно «подписать» этот класс на протокол Observer и реализовать необходимый метод:

// Objective-C

// в файле заголовка CurrentConditionsDisplay.h
@interface CurrentConditionsDisplay : NSObject <Observer>

// в файле реализации CurrentConditionsDisplay.m
- (void)updateWithTemperature:(double)temperature
                     humidity:(double)humidity
                  andPressure:(double)pressure {

    self.temperature = temperature;
    self.humidity = humidity;
    [self display];
}
// Swift
class CurrentConditionsDisplay: Observer {

    func update(with temperature: Double, humidity: Double, and pressure: Double) {
        self.temperature = temperature
        self.humidity = humidity
        self.display()
    }

Почти готово. Осталось зарегистрировать нашего наблюдателя у субъекта (также не забывайте удалять регистрацию при деинициализации).

Для этого нам понадобится еще одно свойство:

// Objective-C
@property (weak, nonatomic) Subject<SubjectProtocol> *weatherData;
// Swift
private weak var weatherData: Subject?

И инициализатор с деинициализатором:

// Objective-C
- (instancetype)initWithSubject:(Subject<SubjectProtocol> *)subject {
    self = [super init];
    if (self) {
        self.weatherData = subject;
        [self.weatherData registerObserver:self];
    }
    return self;
}

- (void)dealloc
{
    [self.weatherData removeObserver:self];
}
// Swift
init(with subject: Subject) {
    self.weatherData = subject
    self.weatherData?.register(observer: self)
}

deinit {
    self.weatherData?.remove(observer: self)
}

Заключение

Мы написали довольно простую реализацию паттерна «Наблюдатель». Наш вариант, конечно же не без изъянов. Например, если мы добавим четвертый датчик, то нужно будет переписывать интерфейс наблюдателей и реализации этого интерфейса (чтоб доставлять до наблюдателей четвертый параметр), а это не есть хорошо. В NotificationCenter, о котором я упоминал в самом начале статьи, такой проблемы не существует. Дело в том, что там передача данных происходит одним-единым параметром-словарем.

Спасибо за внимание, учитесь и учите других.
Ведь пока мы учимся — мы остаемся молодыми. 🙂

Реклама

Паттерны проектирования, взгляд iOS разработчика. Часть 1. Стратегия

Содержание:

Часть 0. Синглтон-Одиночка
Часть 1. Стратегия
Часть 2. Наблюдатель

Напомню, что в этой серии статей, я разбираю книгу «Паттерны проектирования» Эрика и Элизабет Фримен. И сегодня мы изучим паттерн «Стратегия». Поехали.

Откуда растут ноги (и крылья)

Авторы книги рассказывают нам историю о создании приложения SimUDuck. Начнем с реализации начального состояния приложения: у нас есть абстрактный класс Duck и два его наследника: MallardDuck и RedheadDuck. Тут же мы сталкиваемся с первой сложностью: в Objective-C и Swift нет абстрактных классов.

Выходим из ситуации теми инструментами, что есть: для Objective-C объявляем обычный класс Duck (в нем будут реализации по умолчанию) и к нему добавляем протокол AbstractDuck (в нем будут абстрактные методы, которые нужно реализовать в наследниках). Выглядит это так:

// Objective-C
@protocol AbstractDuck <NSObject>

- (void)display;

@end

@interface Duck : NSObject

- (void)quack;
- (void)swim;

@end

Соответственно наследники будут такими:

// Objective-C
@interface MallardDuck : Duck <AbstractDuck>

@end

@implementation MallardDuck

- (void)display {

}

@end

@interface RedheadDuck : Duck <AbstractDuck>

@end

@implementation RedheadDuck

- (void)display {

}

@end

В Swift это сделать немного проще: достаточно протокола и его расширения (в расширении можно некоторые методы протокола реализовать по умолчанию):

// Swift
protocol Duck {
    func quack()
    func swim()
    func display()
}

extension Duck {

    func quack() {

    }

    func swim() {

    }

}

И наследники:

// Swift
class MallardDuck: Duck {

    func display() {

    }

}

class RedheadDuck: Duck {

    func display() {

    }

}

Приложение развивается и у уток появляется возможность летать

Для этого соответствующий метод появляется в родительском классе Duck. И вскоре после этого выясняется, что есть еще один наследник — RubberDuck. Резиновые утки ведь не летают, а поскольку метод добавлен в родительский класс, то он будет доступен и для резиновых уток. В общем: ситуация оказалась не из простых. При дальнейшем расширении приложения будут возникать сложности с поддержкой функций полета (и не только с ней, с функцией кряканья та же история) и с другими видами уток (деревянных, например).

Сначала авторы книги предлагают решать проблему вынесением функций полета и кряканья в отдельные интерфейсы (для Objective-c и Swift — протоколы) Flyable и Quackable. Но этот вариант оказывается совсем не так хорош, каким кажется на первый взгляд. Малейшее изменение функции полета, которое должно быть применено ко всем летающим уткам влечет за собой внесение одного и того же кода во многих местах программы. Так что такое решение определенно не подходит.

(говоря о негодности этого варианта, авторы ссылаются на то, что в интерфейсах (для нас протоколах) нет реализаций по умолчанию, но это справедливо лишь для Objective-C, а вот в Swift реализации по умолчанию для полета и кряканья можно было бы написать в расширениях этих протоколов и переопределять эти функции только там где необходимо, а не везде)

Ну и к тому же, одна из главных целей паттерна — подменяемая реализация поведений во время выполнения, поэтому авторы предлагают выносить реализации поведений из класса Duck.

Для этого создадим протоколы FlyBehavior и QuackBehavior:

// Objective-C
@protocol FlyBehavior <NSObject>

- (void)fly;

@end

@protocol QuackBehavior <NSObject>

- (void)quack;

@end
// Swift
protocol FlyBehavior {
    func fly()
}

protocol QuackBehavior {
    func quack()
}

И конкретные классы реализующие эти протоколы: FlyWithWings и FlyNoWay для FlyBehavior, а также Quack, Squeak и MuteQuack для QuackBehavior (приведу пример для FlyWithWings, остальные реализуются очень схожим образом) :

// Objective-C
@interface FlyWithWings : NSObject <FlyBehavior>

@end

@implementation FlyWithWings

- (void)fly {

    // fly implementation

}

@end
// Swift
class FlyWithWings: FlyBehavior {

    func fly() {

        // fly implementation

    }

}

Делегирование наше все

Теперь мы, по сути, делегируем наше поведение любому другому классу, который реализует соответствующий интерфейс (протокол). Как сказать нашей утке каким должно быть ее поведение в полете и при кряканьи? Очень просто, добавляем в наш класс (в Swift — протокол) Duck два свойства:

// Objective-C
@property (strong, nonatomic) id<FlyBehavior> flyBehavior;
@property (strong, nonatomic) id<QuackBehavior> quackBehavior;
// Swift
var flyBehavior: FlyBehavior { get set }
var quackBehavior: QuackBehavior { get set }

Как видите у них не определен конкретный тип, определено лишь, что это класс подписанный на соответствующий протокол.

Методы fly и quack нашего родительского класса (или протокола) Duck заменим аналогичными:

// Objective-C
- (void)performFly {
    [self.flyBehavior fly];
}

- (void)performQuack {
    [self.quackBehavior quack];
}
// Swift
func performFly() {
    flyBehavior.fly()
}

func performQuack() {
    quackBehavior.quack()
}

Теперь наша утка просто делегирует свое поведение соответствующему поведенческому объекту. Как мы устанавливаем поведение каждой утке? Например при инициализации (пример для MallardDuck):

// Objective-C
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.flyBehavior = [[FlyWithWings alloc] init];
        self.quackBehavior = [[Quack alloc] init];
    }
    return self;
}
// Swift
init() {
    self.flyBehavior = FlyWithWings()
    self.quackBehavior = Quack()
}

Наш паттерн готов 🙂

Заключение

В iOS разработке паттерн «Стратегия» вы можете встретить, например, в архитектуре MVP: в ней презентер является не чем иным как поведенческим объектом для вью-контроллера (вью-контроллер, как вы помните, только сообщает презентеру о действиях пользователя, а вот логику обработки данных определяет презентер), и наоборот: вью-контроллер — поведенческий объект для презентера (презентер лишь говорит «показать пользователю данные», но как именно они будут показаны — решит вью-контроллер). Также этот паттерн вы встретите и в VIPER, если, конечно, надумаете его использовать в вашем приложении. 🙂

Паттерны проектирования, взгляд iOS разработчика. Часть 0. Синглтон-Одиночка

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

Небольшое вступление.

Я не зря вынес в начало поста цитату на украинском языке. Дело в том, что именно эти слова я услышал от своего преподавателя программирования на втором курсе университета, и именно в таком виде я вспоминаю эти слова до сих пор. Как вы можете догадаться, эта цитата является отсылкой к высказыванию Конфуция, но в ней есть очень важное дополнение о достижении мастерства.

И именно эти слова и сподвигли меня на написание данной серии постов. Дело в том, что я — начинающий 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() {}

}

П.С.

Хочу попросить всех читателей и комментаторов: если вы увидели какую-нибудь неточность или знаете как какой-либо из приведенных мною кусков кода написать правильнее/красивее/корректнее — скажите об этом. Я пишу здесь совсем не для того, чтоб учить других, а для того, чтоб научиться самому. Ведь пока мы учимся — мы остаемся молодыми.

Спасибо вам за внимание.

Какао-Макао.

Кто в детстве не пил какао? Я думаю, что пили все. Я вообще мог пить три раза в день: на завтрак в обед и ужин. Потом какао сменилось на кофе, а потом вместо хорошего черного кофе полился не менее хороший и не менее черный чай.

Но какао в моей жизни осталось, оно просто приобрело другую форму.

Встречайте крайне необходимую вещь для любого iOS разработчика — CocoaPods.

CocoaPods — это отличный инструмент для использования сторонних библиотек в своем приложении. Причем работать с CocoaPods настолько просто, что даже ребенок разберется за 15 минут.

Изначально CocoaPods, конечно же, заточен под работу с Objective-C, но это ведь не значит, что и с языком Swift он не будет взаимодействовать (просто нужно добавить в проект bridge-файл, через который подключаются библиотеки).

В общем: с использованием CocoaPods возможности приложений стремительно расширяются, а разработчику становится намного легче добавлять новые функции.

Ну, и конечно, статья про CocoaPods на «Хабре»: CocоaPods — мощное средство в руках Objective-C разработчика

Ссылки, ссылки, ссылочки.

За последнее время у меня собралось много материалов, которые я, в первую очередь, хотел бы сохранить для себя. Ну и заодно — поделиться с общественностью, может кому-то это будет полезно, может кто-то узнает что-то новое или посмотрит на какую-то вещь другим взглядом.

Начнем. Сначала у нас очень хороший видос про команды терминала. «Маководы» в целом, и iOS разработчики в частности, регулярно пользуются терминалом, поэтому небольшая обучалка командам точно будет не лишней:

На втором месте — отличная статья с «Хабра» на тему разработки: 7 смертных грехов программного проекта. Почитайте обязательно, много интересного и полезного.

И, конечно же, Архитектурные паттерны в iOS все с того же «Хабра», куда ж без них. Не MVC единым как говорится. 🙂

Ну и напоследок — немного мотивации от Стива:

А от себя добавлю: учитесь, пока вы учитесь — вы молоды.

Objective-C. Курс для начинающего.

Есть очень клевый парень Алексей Скутаренко. Он живет в штатах, и периодически выкладывает уроки по разработке для iOS.

Для начала, рекомендую посмотреть два его видеоурока по ОПП. Дело в том, что Алексей объясняет довольно быстро и, лучше сначала посмотреть что-нибудь более-менее простое в его исполнении, чтоб привыкнуть к скорости и стилю изложения (тогда все последующие видео будут восприниматься лучше).

А вот и сам курс по Objective-C для «бегиннеров». Наслаждайтесь:

Оооп!

Естественно, для разработки мобильных приложений необходимы знания объектно-ориентированного программирования. Я недолго искал их на «ютюбе».

«Основы программирования»:

«Основы ООП»:

И, собственно, примеры для Objective-C: