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

172

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

cescofry
источник
1
Недавно я нашел статью от Мэтта Галлоуэя, в которой достаточно подробно рассказывается о синглетонах как для ARC, так и для ручного управления памятью. galloway.me.uk/tutorials/singleton-classes
cescofry

Ответы:

391

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

+ (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;
}
Ник Фордж
источник
9
Вы просто не выполняете никаких манипуляций с ключами в управлении памятью, которые Apple рекомендовала в developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…
Кристофер Пиксли
1
@MakingScienceFictionFact, возможно, вы захотите взглянуть на этот пост
kervich
6
staticПеременные @David, объявленные в методе / функции, аналогичны staticпеременной, объявленной вне метода / функции, они действительны только в рамках этого метода / функции. Каждый отдельный прогон +sharedInstanceметода (даже в разных потоках) будет «видеть» одну и ту же sharedInstanceпеременную.
Ник Фордж
9
А что если кто-то позвонит [[MyClass alloc] init]? Это создаст новый объект. Как мы можем избежать этого (кроме объявления статического MyClass * sharedInstance = nil вне метода).
Рикардо Санчес-Саез
2
Если другой программист запутался и вызвал init, когда они должны были вызвать sharedInstance или подобное, это их ошибка. Подрыв основных принципов и базовых контрактов языка с целью предотвращения возможного совершения другими ошибками кажется совершенно неправильным. Больше обсуждений на boredzo.org/blog/archives/2009-06-17/doing-it-wrong
occulus
8

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

+ (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;
}
Dongxu
источник
1
True / False: dispatch_once()бит означает, что вы не получите дополнительные экземпляры, даже в первом примере ...?
Оли
4
@ Оли: Ложь, потому что клиентский код может сделать [[MyClass alloc] init]и обойти sharedInstanceдоступ. DongXu, вы должны посмотреть на статью Питера Хоси Singleton . Если вы собираетесь переопределить, allocWithZone:чтобы предотвратить создание большего количества экземпляров, вы также должны переопределить, initчтобы предотвратить повторную инициализацию общего экземпляра.
ОАО
Хорошо, я так и думал, отсюда и allocWithZone:версия. Спасибо.
Оли
2
Это полностью нарушает контракт на allocWithZone.
Occulus
1
Синглтон просто означает «только один объект в памяти в любое время», это одно, а переинициализация - это другое.
DongXu
5

Это версия для 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
Игорь
источник
2

Это мой шаблон под 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
eonil
источник
1
Не приведет ли это к тому, c1что он станет AAAсуперклассом России? Вам нужно позвонить +allocна self, а не на super.
Ник Фордж
@NickForge superне означает объект суперкласса. Вы не можете получить объект суперкласса. Это просто означает маршрутизацию сообщений в версию метода суперкласса. superвсе еще указывает selfкласс. Если вы хотите получить объект суперкласса, вам нужно получить функции отражения во время выполнения.
Эонил
@NickForge И -allocWithZone:метод - это просто простая цепочка для функции выделения времени выполнения, чтобы предложить точку переопределения. В конечном итоге selfуказатель == текущий объект класса будет передан распределителю, и, наконец, AAAэкземпляр будет выделен.
Эонил
Вы правы, я забыл тонкости того, как superработает в методах класса.
Ник Фордж
Не забывайте использовать #import <objc / objc-runtime.h>
Райан Хейтнер,
2

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

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

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

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

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

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

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

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

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

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

Мед
источник
1

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

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

Уолт Селлерс
источник
0

Класс 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, init вызовет sharedInstance, sharedInstance вызовет init, init повторно вызовет sharedInstance, а затем произойдет сбой! Во-первых, это бесконечный цикл рекурсии. Во-вторых, вторая итерация вызова dispatch_once завершится сбоем, потому что она не может быть вызвана снова из dispatch_once.
Чак Круцингер
0

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

  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 stackoverflow 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;
}
Вернер Альтевишер
источник
-2
#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

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

Киран
источник
-2

если вам нужно создать синглтон в 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
muhammedkasva
источник