Каким должен быть мой синглтон Objective-C?
Мой однопользовательский метод доступа обычно представляет собой некоторый вариант:
static MyClass *gInstance = NULL;
+ (MyClass *)instance
{
@synchronized(self)
{
if (gInstance == NULL)
gInstance = [[self alloc] init];
}
return(gInstance);
}
Что я могу сделать, чтобы улучшить это?
Ответы
Ответ 1
Другой вариант - использовать метод +(void)initialize
. Из документации:
Время выполнения отправляет initialize
каждому классу в программе ровно один раз перед классом или любым наследуемым от него классом, отправляется его первое сообщение из программы. (Таким образом, этот метод никогда не может быть вызван, если класс не используется.) Среда выполнения отправляет сообщение initialize
классам поточно-безопасным образом. Суперклассы получают это сообщение перед их подклассами.
Итак, вы можете сделать что-то похожее на это:
static MySingleton *sharedSingleton;
+ (void)initialize
{
static BOOL initialized = NO;
if(!initialized)
{
initialized = YES;
sharedSingleton = [[MySingleton alloc] init];
}
}
Ответ 2
@interface MySingleton : NSObject
{
}
+ (MySingleton *)sharedSingleton;
@end
@implementation MySingleton
+ (MySingleton *)sharedSingleton
{
static MySingleton *sharedSingleton;
@synchronized(self)
{
if (!sharedSingleton)
sharedSingleton = [[MySingleton alloc] init];
return sharedSingleton;
}
}
@end
[Источник]
Ответ 3
В соответствии с моим другим ответом ниже я думаю, что вы должны делать:
+ (id)sharedFoo
{
static dispatch_once_t once;
static MyFoo *sharedFoo;
dispatch_once(&once, ^ { sharedFoo = [[self alloc] init]; });
return sharedFoo;
}
Ответ 4
Так как Kendall опубликовал потоковый потоковый синглтон, который пытается избежать блокировки затрат, я думал, что тоже подброшу его:
#import <libkern/OSAtomic.h>
static void * volatile sharedInstance = nil;
+ (className *) sharedInstance {
while (!sharedInstance) {
className *temp = [[self alloc] init];
if(!OSAtomicCompareAndSwapPtrBarrier(0x0, temp, &sharedInstance)) {
[temp release];
}
}
return sharedInstance;
}
Хорошо, позвольте мне объяснить, как это работает:
-
Быстрый случай: при нормальном выполнении sharedInstance
уже установлен, поэтому цикл while
никогда не выполняется и функция возвращается после простого тестирования существования переменной;
-
Медленный случай: если sharedInstance
не существует, то экземпляр выделяется и копируется в него с помощью Compare and Swap ('CAS');
-
Конфликтный случай: если два потока одновременно пытаются вызвать sharedInstance
одновременно, а sharedInstance
не существует одновременно, то они оба будут инициализировать новые экземпляры одноэлементного объекта и попытаться использовать CAS it в положение. Какой бы выигрыш CAS не возвращался немедленно, в зависимости от того, кто теряет релизы, экземпляр, который он только что выделил, и возвращает (теперь установленный) sharedInstance
. Единственный OSAtomicCompareAndSwapPtrBarrier
действует как барьер записи для установочного потока и барьер чтения из тестового потока.
Ответ 5
static MyClass *sharedInst = nil;
+ (id)sharedInstance
{
@synchronize( self ) {
if ( sharedInst == nil ) {
/* sharedInst set up in init */
[[self alloc] init];
}
}
return sharedInst;
}
- (id)init
{
if ( sharedInst != nil ) {
[NSException raise:NSInternalInconsistencyException
format:@"[%@ %@] cannot be called; use +[%@ %@] instead"],
NSStringFromClass([self class]), NSStringFromSelector(_cmd),
NSStringFromClass([self class]),
NSStringFromSelector(@selector(sharedInstance)"];
} else if ( self = [super init] ) {
sharedInst = self;
/* Whatever class specific here */
}
return sharedInst;
}
/* These probably do nothing in
a GC app. Keeps singleton
as an actual singleton in a
non CG app
*/
- (NSUInteger)retainCount
{
return NSUIntegerMax;
}
- (oneway void)release
{
}
- (id)retain
{
return sharedInst;
}
- (id)autorelease
{
return sharedInst;
}
Ответ 6
Изменить: эта реализация устарела с помощью ARC. Пожалуйста, посмотрите Как реализовать синглтон Objective-C, совместимый с ARC? для правильной реализации.
Все реализации инициализации, которые я прочитал в других ответах, имеют общую ошибку.
+ (void) initialize {
_instance = [[MySingletonClass alloc] init] // <----- Wrong!
}
+ (void) initialize {
if (self == [MySingletonClass class]){ // <----- Correct!
_instance = [[MySingletonClass alloc] init]
}
}
Документация Apple рекомендует вам проверить тип класса в вашем блоке инициализации. Поскольку подклассы вызывают инициализацию по умолчанию. Существует неочевидный случай, когда подклассы могут создаваться косвенно через KVO. Если вы добавили следующую строку в другой класс:
[[MySingletonClass getInstance] addObserver:self forKeyPath:@"foo" options:0 context:nil]
Objective-C будет неявно создавать подкласс MySingletonClass, что приведет к второму запуску +initialize
.
Вы можете подумать, что вы должны неявно проверять дублируемую инициализацию в своем блоке init как таковой:
- (id) init { <----- Wrong!
if (_instance != nil) {
// Some hack
}
else {
// Do stuff
}
return self;
}
Но вы стреляете в ногу; или, что еще хуже, дать другому разработчику возможность стрелять в ногу.
- (id) init { <----- Correct!
NSAssert(_instance == nil, @"Duplication initialization of singleton");
self = [super init];
if (self){
// Do stuff
}
return self;
}
TL; DR, здесь моя реализация
@implementation MySingletonClass
static MySingletonClass * _instance;
+ (void) initialize {
if (self == [MySingletonClass class]){
_instance = [[MySingletonClass alloc] init];
}
}
- (id) init {
ZAssert (_instance == nil, @"Duplication initialization of singleton");
self = [super init];
if (self) {
// Initialization
}
return self;
}
+ (id) getInstance {
return _instance;
}
@end
(Замените ZAssert на наш собственный макрос утверждения или просто NSAssert.)
Ответ 7
Подробное объяснение макрокоманды Singleton находится в блоге Cocoa With Love
http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html.
Ответ 8
У меня есть интересный вариант на sharedInstance, который является потокобезопасным, но не блокируется после инициализации. Я еще не достаточно уверен, чтобы изменить верхний ответ в соответствии с запросом, но я представляю его для дальнейшего обсуждения:
// Volatile to make sure we are not foiled by CPU caches
static volatile ALBackendRequestManager *sharedInstance;
// There no need to call this directly, as method swizzling in sharedInstance
// means this will get called after the singleton is initialized.
+ (MySingleton *)simpleSharedInstance
{
return (MySingleton *)sharedInstance;
}
+ (MySingleton*)sharedInstance
{
@synchronized(self)
{
if (sharedInstance == nil)
{
sharedInstance = [[MySingleton alloc] init];
// Replace expensive thread-safe method
// with the simpler one that just returns the allocated instance.
SEL origSel = @selector(sharedInstance);
SEL newSel = @selector(simpleSharedInstance);
Method origMethod = class_getClassMethod(self, origSel);
Method newMethod = class_getClassMethod(self, newSel);
method_exchangeImplementations(origMethod, newMethod);
}
}
return (MySingleton *)sharedInstance;
}
Ответ 9
Короткий ответ: сказочный.
Длинный ответ: Что-то вроде....
static SomeSingleton *instance = NULL;
@implementation SomeSingleton
+ (id) instance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (instance == NULL){
instance = [[super allocWithZone:NULL] init];
}
});
return instance;
}
+ (id) allocWithZone:(NSZone *)paramZone {
return [[self instance] retain];
}
- (id) copyWithZone:(NSZone *)paramZone {
return self;
}
- (id) autorelease {
return self;
}
- (NSUInteger) retainCount {
return NSUIntegerMax;
}
- (id) retain {
return self;
}
@end
Обязательно прочтите заголовок dispatch/once.h, чтобы понять, что происходит. В этом случае комментарии заголовка более применимы, чем документы или справочная страница.
Ответ 10
Я включил singleton в класс, поэтому другие классы могут наследовать свойства singleton.
Singleton.h:
static id sharedInstance = nil;
#define DEFINE_SHARED_INSTANCE + (id) sharedInstance { return [self sharedInstance:&sharedInstance]; } \
+ (id) allocWithZone:(NSZone *)zone { return [self allocWithZone:zone forInstance:&sharedInstance]; }
@interface Singleton : NSObject {
}
+ (id) sharedInstance;
+ (id) sharedInstance:(id*)inst;
+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst;
@end
Синглтон .m:
#import "Singleton.h"
@implementation Singleton
+ (id) sharedInstance {
return [self sharedInstance:&sharedInstance];
}
+ (id) sharedInstance:(id*)inst {
@synchronized(self)
{
if (*inst == nil)
*inst = [[self alloc] init];
}
return *inst;
}
+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst {
@synchronized(self) {
if (*inst == nil) {
*inst = [super allocWithZone:zone];
return *inst; // assignment and return on first allocation
}
}
return nil; // on subsequent allocation attempts return nil
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX; // denotes an object that cannot be released
}
- (void)release {
//do nothing
}
- (id)autorelease {
return self;
}
@end
И вот пример некоторого класса, который вы хотите стать одиночным.
#import "Singleton.h"
@interface SomeClass : Singleton {
}
@end
@implementation SomeClass
DEFINE_SHARED_INSTANCE;
@end
Единственное ограничение для класса Singleton - это подкласс NSObject. Но чаще всего я использую синглтоны в своем коде, они на самом деле являются подклассами NSObject, поэтому этот класс действительно облегчает мою жизнь и делает код чистым.
Ответ 11
Это также работает в среде, не связанной с мусором.
@interface MySingleton : NSObject {
}
+(MySingleton *)sharedManager;
@end
@implementation MySingleton
static MySingleton *sharedMySingleton = nil;
+(MySingleton*)sharedManager {
@synchronized(self) {
if (sharedMySingleton == nil) {
[[self alloc] init]; // assignment not done here
}
}
return sharedMySingleton;
}
+(id)allocWithZone:(NSZone *)zone {
@synchronized(self) {
if (sharedMySingleton == nil) {
sharedMySingleton = [super allocWithZone:zone];
return sharedMySingleton; // assignment and return on first allocation
}
}
return nil; //on subsequent allocation attempts return nil
}
-(void)dealloc {
[super dealloc];
}
-(id)copyWithZone:(NSZone *)zone {
return self;
}
-(id)retain {
return self;
}
-(unsigned)retainCount {
return UINT_MAX; //denotes an object that cannot be release
}
-(void)release {
//do nothing
}
-(id)autorelease {
return self;
}
-(id)init {
self = [super init];
sharedMySingleton = self;
//initialize here
return self;
}
@end
Ответ 12
Вот макрос, который я собрал:
http://github.com/cjhanson/Objective-C-Optimized-Singleton
Он основан на работе здесь Мэтта Галлахера
Но изменение реализации для использования метода swizzling, как описано здесь Дейвом МакЛакланом из Google.
Я приветствую комментарии/вклады.
Ответ 13
Неужели это будет потокобезопасным и избежать дорогостоящей блокировки после первого вызова?
+ (MySingleton*)sharedInstance
{
if (sharedInstance == nil) {
@synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [[MySingleton alloc] init];
}
}
}
return (MySingleton *)sharedInstance;
}
Ответ 14
Для подробного обсуждения одноэлементного рисунка в Objective-C, смотрите здесь:
Использование шаблона Singleton в Objective-C
Ответ 15
Как насчет
static MyClass *gInstance = NULL;
+ (MyClass *)instance
{
if (gInstance == NULL) {
@synchronized(self)
{
if (gInstance == NULL)
gInstance = [[self alloc] init];
}
}
return(gInstance);
}
Итак, вы избегаете стоимости синхронизации после инициализации?
Ответ 16
KLSingleton:
- Подклассы (до n-й степени)
- Совместимость с ARC
- Безопасно с
alloc
и init
- Загружено лениво
- потокобезопасна
- Без блокировки (использует + инициализировать, а не @синхронизировать)
- Macro свободной
- Swizzle свободной
- Простой
KLSingleton
Ответ 17
Вы не хотите синхронизировать себя... Поскольку самообъект еще не существует! В конечном итоге вы блокируете временное значение id. Вы хотите, чтобы никто не мог запускать методы класса (sharedInstance, alloc, allocWithZone:, и т.д.), Поэтому вам нужно синхронизировать объект класса:
@implementation MYSingleton
static MYSingleton * sharedInstance = nil;
+( id )sharedInstance {
@synchronized( [ MYSingleton class ] ) {
if( sharedInstance == nil )
sharedInstance = [ [ MYSingleton alloc ] init ];
}
return sharedInstance;
}
+( id )allocWithZone:( NSZone * )zone {
@synchronized( [ MYSingleton class ] ) {
if( sharedInstance == nil )
sharedInstance = [ super allocWithZone:zone ];
}
return sharedInstance;
}
-( id )init {
@synchronized( [ MYSingleton class ] ) {
self = [ super init ];
if( self != nil ) {
// Insert initialization code here
}
return self;
}
}
@end
Ответ 18
static mySingleton *obj=nil;
@implementation mySingleton
-(id) init {
if(obj != nil){
[self release];
return obj;
} else if(self = [super init]) {
obj = self;
}
return obj;
}
+(mySingleton*) getSharedInstance {
@synchronized(self){
if(obj == nil) {
obj = [[mySingleton alloc] init];
}
}
return obj;
}
- (id)retain {
return self;
}
- (id)copy {
return self;
}
- (unsigned)retainCount {
return UINT_MAX; // denotes an object that cannot be released
}
- (void)release {
if(obj != self){
[super release];
}
//do nothing
}
- (id)autorelease {
return self;
}
-(void) dealloc {
[super dealloc];
}
@end
Ответ 19
Просто хотел оставить это здесь, чтобы не потерять его. Преимущество этого заключается в том, что его можно использовать в интерфейсе InterfaceBuilder, который является ОГРОМНЫМ преимуществом. Это взято из другого вопроса, который я задал:
static Server *instance;
+ (Server *)instance { return instance; }
+ (id)hiddenAlloc
{
return [super alloc];
}
+ (id)alloc
{
return [[self instance] retain];
}
+ (void)initialize
{
static BOOL initialized = NO;
if(!initialized)
{
initialized = YES;
instance = [[Server hiddenAlloc] init];
}
}
- (id) init
{
if (instance)
return self;
self = [super init];
if (self != nil) {
// whatever
}
return self;
}
Ответ 20
Я знаю, что есть много комментариев по этому "вопросу", но я не вижу, чтобы многие люди предлагали использовать макрос для определения синглтона. Такой общий шаблон и макрос значительно упрощают синглтон.
Вот макросы, которые я написал на основе нескольких реализаций Objc, которые я видел.
Singeton.h
/**
@abstract Helps define the interface of a singleton.
@param TYPE The type of this singleton.
@param NAME The name of the singleton accessor. Must match the name used in the implementation.
@discussion
Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
*/
#define SingletonInterface(TYPE, NAME) \
+ (TYPE *)NAME;
/**
@abstract Helps define the implementation of a singleton.
@param TYPE The type of this singleton.
@param NAME The name of the singleton accessor. Must match the name used in the interface.
@discussion
Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
*/
#define SingletonImplementation(TYPE, NAME) \
static TYPE *__ ## NAME; \
\
\
+ (void)initialize \
{ \
static BOOL initialized = NO; \
if(!initialized) \
{ \
initialized = YES; \
__ ## NAME = [[TYPE alloc] init]; \
} \
} \
\
\
+ (TYPE *)NAME \
{ \
return __ ## NAME; \
}
Пример использования:
MyManager.h
@interface MyManager
SingletonInterface(MyManager, sharedManager);
// ...
@end
MyManager.m
@implementation MyManager
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
}
return self;
}
SingletonImplementation(MyManager, sharedManager);
// ...
@end
Почему макрос интерфейса, когда он почти пуст? Кодовая согласованность между заголовком и файлами кода; ремонтируемость, если вы хотите добавить больше автоматических методов или изменить их вокруг.
Я использую метод initialize для создания singleton, который используется в наиболее популярном здесь ответе (на момент написания).
Ответ 21
С помощью методов класса Objective C мы можем просто избегать использования одноэлементного шаблона обычным способом, начиная с:
[[Librarian sharedInstance] openLibrary]
в
[Librarian openLibrary]
обернув класс внутри другого класса, который имеет только методы класса, таким образом, нет случайности создания повторяющихся экземпляров, поскольку мы не создаем какой-либо экземпляр!
Я написал более подробный блог здесь:)
Ответ 22
Чтобы расширить пример от @robbie-hanson...
static MySingleton* sharedSingleton = nil;
+ (void)initialize {
static BOOL initialized = NO;
if (!initialized) {
initialized = YES;
sharedSingleton = [[self alloc] init];
}
}
- (id)init {
self = [super init];
if (self) {
// Member initialization here.
}
return self;
}
Ответ 23
Мой способ прост:
static id instanceOfXXX = nil;
+ (id) sharedXXX
{
static volatile BOOL initialized = NO;
if (!initialized)
{
@synchronized([XXX class])
{
if (!initialized)
{
instanceOfXXX = [[XXX alloc] init];
initialized = YES;
}
}
}
return instanceOfXXX;
}
Если синглтон уже инициализирован, блок LOCK не будет введен. Вторая проверка, если (! Initialized), чтобы убедиться, что она еще не инициализирована, когда текущий поток получает LOCK.
Ответ 24
Я не читал все решения, поэтому прощайте, если этот код лишний.
Это, по моему мнению, самая надежная реализация.
+(SingletonObject *) sharedManager
{
static SingletonObject * sharedResourcesObj = nil;
@synchronized(self)
{
if (!sharedResourcesObj)
{
sharedResourcesObj = [[SingletonObject alloc] init];
}
}
return sharedResourcesObj;
}
Ответ 25
Я обычно использую код, примерно такой же, как в ответе Бен Хоффштейна (который я также получил из Википедии). Я использую его по причинам, изложенным Крисом Хэнсоном в его комментарии.
Однако иногда мне нужно помещать singleton в NIB, и в этом случае я использую следующее:
@implementation Singleton
static Singleton *singleton = nil;
- (id)init {
static BOOL initialized = NO;
if (!initialized) {
self = [super init];
singleton = self;
initialized = YES;
}
return self;
}
+ (id)allocWithZone:(NSZone*)zone {
@synchronized (self) {
if (!singleton)
singleton = [super allocWithZone:zone];
}
return singleton;
}
+ (Singleton*)sharedSingleton {
if (!singleton)
[[Singleton alloc] init];
return singleton;
}
@end
Я оставляю для читателя реализацию -retain
(и т.д.), хотя приведенный выше код - это все, что вам нужно в среде сбора мусора.
Ответ 26
Принятый ответ, хотя он компилируется, неверен.
+ (MySingleton*)sharedInstance
{
@synchronized(self) <-------- self does not exist at class scope
{
if (sharedInstance == nil)
sharedInstance = [[MySingleton alloc] init];
}
return sharedInstance;
}
Документация на Apple:
... Вы можете использовать аналогичный подход для синхронизации методов класса связанного класса, используя объект Class вместо self.
Даже если вы используете self works, это не должно быть, и это похоже на ошибку копирования и вставки.
Правильной реализацией метода класса factory будет:
+ (MySingleton*)getInstance
{
@synchronized([MySingleton class])
{
if (sharedInstance == nil)
sharedInstance = [[MySingleton alloc] init];
}
return sharedInstance;
}