Начнем с вопроса — «Что такое и зачем нужен Singleton?». Singleton (Одиночка) — это порождающий шаблон проектирования. Singleton гарантирует, что у класса есть только один рабочий экземпляр и предоставляет к нему доступ одним единственным способом. Singleton доступен во всем приложении.

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

Apple в iOS SDK использует Singleton в очень многих местах. Посмотрим:


NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];
UIApplication* myApplication = [UIApplication sharedApplication];

Как видим, с этими классами, как с Singleton, мы работаем довольно часто.

Что нам нужно для реализации Singleton? Для начала создадим класс MySingleton:

h-файл:


@interface MySingleton : NSObject
{
}
+ (MySingleton *) sharedInstance;
@end

Метод + (MySingleton *) sharedInstance будет использоваться для доступа к единственному экземпляру. Конечно, подразумевается, что все, кто будут использовать класс, будут вызывать именно этот метод, для получения экземпляра. Но, я думаю, найдутся и те, кто будет вызывать alloc-init. И об это стоит позаботиться.

Начнем реализовывать класс, сделаем метод доступа к экземпляру (m-файл):


@implementation MySingleton
static MySingleton *sMySingleton = nil;
+ (MySingleton *) sharedInstance
{
@synchronized(self)
{
if (sMySingleton == nil)
{
sMySingleton = [NSAllocateObject([self class], 0, NULL) init];
}
}

return sMySingleton;
}
@end

Обратим внимание на:

static MySingleton *sMySingleton = nil;

@synchronized — синхронизирует доступ из разных потоков.

Эта переменная будет хранить наш единственный экземпляр. Что делает этот метод? Он проверяет, создан ли уже экзмепляр или нет, и если создан возвращает старый, если нет — создает его. В принципе, этого может хватить, но это еще не все.

У вас наверное мог возникнуть вопрос, почему нужно использовать

sMySingleton = [NSAllocateObject([self class], 0, NULL) init];

а не

sMySingleton = [[MySingleton alloc] init];

Все дело в том, что мы решили «защищаться» от вызовов alloc-init и игнорировать их, возвращая старый объект. И alloc-init нам больше нельзя использовать. Чтобы выделить память мы используем именно NSAllocateObject. Реализуем эту защиту. Конечно же, нам нужно перекрыть метод alloc, заодно и перекроем copy (допишите в реализацию класса, m-файл):


+ (id) allocWithZone:(NSZone *)zone
{
return [[self sharedInstance] retain];
}
- (id) copyWithZone:(NSZone*)zone
{
return self;
}

Т.к. alloc метод статический, то мы должны вернуть уже созданный экземпляр. В copy мы можем вернуть себя (self). Вот это и есть защита от alloc-init и copy. Теперь даже если вызывать эти методы, класс не будет создаваться, а мы всегда получим старый экземпляр.

Осталось «защититься» от вызовов release, retain, autorelease, т.к. мы подразумеваем, что объект существует всегда и на протяжении всей жизни приложения — значит он не должен быть выгружен из памяти:


- (id) retain
{
return self;
}
- (NSUInteger) retainCount
{
return NSUIntegerMax;
}
- (void) release { }
- (id) autorelease
{
return self;
}

Что мы сделали? Мы перекрыли release, который теперь ничего не делает, а значит не уменьшает счетчик ссылок. Мы перекрыли autorelease, который тоже ничего не делает, так же и retain. И сделали так, чтобы счетчик ссылок всегда возвращал максимальное число.

Вот и все, Singleton готов. Проверим, как это работает. Добавим в класс переменную int counter и метод -(void) count. Метод будет увеличивать counter на еденицу при вызове и печатать значение counter.

h-файл:


@interface MySingleton : NSObject
{
int counter;
}
-(void) count;
+ (MySingleton *) sharedInstance;
@end

Допишите в m-файл:


-(void) count
{
NSLog(@"Counter: %d", ++counter);
}

Для проверки того, что мы действительно работаем с одним экземпляром, воспользуемся кодом:


MySingleton* singleton = [[MySingleton alloc] init];
[singleton count];
[singleton count];

singleton = [MySingleton sharedInstance];
[singleton count];

singleton = [[MySingleton alloc] init];
[singleton count];

Тут мы и посылаем alloc-init, и требуем sharedInstance. Запустим и посмотрим, что появилось в консоли:

2012-04-14 14:07:21.036 Singleton[15661:f803] Counter: 1
2012-04-14 14:07:21.038 Singleton[15661:f803] Counter: 2
2012-04-14 14:07:21.058 Singleton[15661:f803] Counter: 3
2012-04-14 14:07:21.061 Singleton[15661:f803] Counter: 4

Действительно, как бы мы не обращались к классу, мы всегда работаем с одним и тем же экземпляром. Ниже вы найдете полный код:

h-файл:


@interface MySingleton : NSObject { }
+ (MySingleton *) sharedInstance;
@end

m-файл:


#import "MySingleton.h"
@implementation MySingleton
static MySingleton *sMySingleton = nil;
+ (MySingleton *) sharedInstance
{
@synchronized(self)
{
if (sMySingleton == nil)
{
sMySingleton = [NSAllocateObject([self class], 0, NULL) init];
}
}

return sMySingleton;
}
+ (id) allocWithZone:(NSZone *)zone
{
return [[self sharedInstance] retain];
}
- (id) copyWithZone:(NSZone*)zone
{
return self;
}
- (id) retain
{
return self;
}
- (NSUInteger) retainCount
{
return NSUIntegerMax;
}
- (void) release { }
- (id) autorelease
{
return self;
}
@end

Данный Singleton обладает еще одним замечательным свойством: если его использовать в Interface Builder как внешний объект, он тоже всегда будет иметь единственный экземпляр. Впрочем, неверное, это тема отдельной статьи.

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *