Как мне реализовать синглтон Objective-C, совместимый с ARC?

Как мне преобразовать (или создать) одноэлементный класс, который компилируется и ведет себя правильно при использовании автоматического подсчета ссылок (ARC) в Xcode 4.2?

10 ответов

Решение

Точно так же, как вы (должны) уже делали это:

+ (instancetype)sharedInstance
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}

Если вы хотите создать другой экземпляр по мере необходимости. сделайте следующее:

+ (MyClass *)sharedInstance
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}

иначе вы должны сделать это:

+ (id)allocWithZone:(NSZone *)zone
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [super allocWithZone:zone];
    });
    return sharedInstance;
}

Это версия для ARC и не ARC

Как пользоваться:

MySingletonClass.h

@interface MySingletonClass : NSObject

+(MySingletonClass *)sharedInstance;

@end

MySingletonClass.m

#import "MySingletonClass.h"
#import "SynthesizeSingleton.h"
@implementation MySingletonClass
SYNTHESIZE_SINGLETON_FOR_CLASS(MySingletonClass)
@end

Прочитайте этот ответ, а затем иди и прочитайте другой ответ.

Сначала вы должны знать, что означает синглтон и каковы его требования, если вы не понимаете его, тогда вы не поймете решение - вообще!

Чтобы успешно создать синглтон, вы должны выполнить следующие 3:

  • Если возникло условие гонки, мы не должны допускать одновременного создания нескольких экземпляров вашего SharedInstance!
  • Помните и сохраняйте значение среди нескольких вызовов.
  • Создайте его только один раз. Управляя точкой входа.

dispatch_once_t помогает вам решить условие гонки, позволяя отправлять блок только один раз.

Static помогает вам "запомнить" его значение при любом количестве вызовов. Как это помнить? Он не позволяет создавать новый экземпляр с таким точным именем вашего sharedInstance, он просто работает с тем, который был создан изначально.

Не использовать звонки allocinit (т.е. у нас еще есть allocinit методы, так как мы являемся подклассом NSObject, хотя мы не должны их использовать) в нашем классе sharedInstance, мы достигаем этого, используя +(instancetype)sharedInstance, который ограничен, чтобы быть инициированным только один раз, независимо от нескольких попыток из разных потоков одновременно и запомнить его значение.

Вот некоторые из наиболее распространенных системных синглетонов, которые поставляются вместе с Какао:

  • [UIApplication sharedApplication]
  • [NSUserDefaults standardUserDefaults]
  • [NSFileManager defaultManager]
  • [NSBundle mainBundle]
  • [NSOperations mainQueue]
  • [NSNotificationCenter defaultCenter]

По сути, все, что должно иметь централизованный эффект, должно следовать какому-то шаблону проектирования Singleton.

Это мой шаблон под ARC. Удовлетворяет новый шаблон, используя GCD, а также удовлетворяет старый шаблон Apple по предотвращению создания экземпляров.

@implementation AAA
+ (id)alloc
{
    return  [self allocWithZone:nil];
}
+ (id)allocWithZone:(NSZone *)zone
{
    [self doesNotRecognizeSelector:_cmd];
    abort();
}
+ (instancetype)theController
{
    static AAA* c1  =   nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
    {
        c1  =   [[super allocWithZone:nil] init];

        // For confirm...       
        NSLog(@"%@", NSStringFromClass([c1 class]));    //  Prints AAA
        NSLog(@"%@", @([c1 class] == self));            //  Prints 1

        Class   real_superclass_obj =   class_getSuperclass(self);
        NSLog(@"%@", @(real_superclass_obj == self));   //  Prints 0
    });

    return  c1;
}
@end

В качестве альтернативы Objective-C предоставляет метод инициализации +(void) для NSObject и всех его подклассов. Он всегда вызывается перед любыми методами класса.

Я установил точку останова в один раз в iOS 6, и dispatch_once появился в кадрах стека.

Класс Singleton: Никто не может создать более одного объекта класса в любом случае или любым способом.

+ (instancetype)sharedInstance
{
    static ClassName *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[ClassName alloc] init];
        // Perform other initialisation...
    });
    return sharedInstance;
}
//    You need need to override init method as well, because developer can call [[MyClass alloc]init] method also. that time also we have to return sharedInstance only. 

-(MyClass)init
{
   return [ClassName sharedInstance];
}

Есть два вопроса с принятым ответом, которые могут или не могут иметь отношение к вашей цели.

  1. Если из метода init каким-то образом вызывается метод sharedInstance (например, из-за того, что из него создаются другие объекты, использующие синглтон), это вызовет переполнение стека.
  2. Для иерархий классов существует только один синглтон (а именно: первый класс в иерархии, для которого был вызван метод sharedInstance) вместо одного синглтона на конкретный класс в иерархии.

Следующий код решает обе эти проблемы:

+ (instancetype)sharedInstance {
    static id mutex = nil;
    static NSMutableDictionary *instances = nil;

    //Initialize the mutex and instances dictionary in a thread safe manner
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mutex = [NSObject new];
        instances = [NSMutableDictionary new];
    });

    id instance = nil;

    //Now synchronize on the mutex
    //Note: do not synchronize on self, since self may differ depending on which class this method is called on
    @synchronized(mutex) {
        id <NSCopying> key = (id <NSCopying>)self;
        instance = instances[key];
        if (instance == nil) {
            //Break allocation and initialization into two statements to prevent a stack overflow, if init somehow calls the sharedInstance method
            id allocatedInstance = [self alloc];

            //Store the instance into the dictionary, one per concrete class (class acts as key for the dictionary)
            //Do this right after allocation to avoid the stackru problem
            if (allocatedInstance != nil) {
                instances[key] = allocatedInstance;
            }
            instance = [allocatedInstance init];

            //Following code may be overly cautious
            if (instance != allocatedInstance) {
                //Somehow the init method did not return the same instance as the alloc method
                if (instance == nil) {
                    //If init returns nil: immediately remove the instance again
                    [instances removeObjectForKey:key];
                } else {
                    //Else: put the instance in the dictionary instead of the allocatedInstance
                    instances[key] = instance;
                }
            }
        }
    }
    return instance;
}
#import <Foundation/Foundation.h>

@interface SingleTon : NSObject

@property (nonatomic,strong) NSString *name;
+(SingleTon *) theSingleTon;

@end

#import "SingleTon.h"
@implementation SingleTon

+(SingleTon *) theSingleTon{
    static SingleTon *theSingleTon = nil;

    if (!theSingleTon) {

        theSingleTon = [[super allocWithZone:nil] init
                     ];
    }
    return theSingleTon;
}

+(id)allocWithZone:(struct _NSZone *)zone{

    return [self theSingleTon];
}

-(id)init{

    self = [super init];
    if (self) {
        // Set Variables
        _name = @"Kiran";
    }

    return self;
}

@end

Надеюсь, что приведенный выше код поможет вам.

Если вам нужно создать синглтон в Swift,

class var sharedInstance: MyClass {
    struct Singleton {
        static let instance = MyClass()
    }
    return Singleton.instance
}

или же

struct Singleton {
    static let sharedInstance = MyClass()
}

class var sharedInstance: MyClass {
    return Singleton.sharedInstance
}

Вы можете использовать этот способ

let sharedClass = LibraryAPI.sharedInstance
Другие вопросы по тегам