Начнем с вопроса — «Что такое и зачем нужен 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 как внешний объект, он тоже всегда будет иметь единственный экземпляр. Впрочем, неверное, это тема отдельной статьи.

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

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

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

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