Як я можу реалізувати сингл-об'єкт Objective-C, сумісний з ARC?


172

Як конвертувати (або створити) однотонний клас, який компілює та веде себе правильно, коли використовує автоматичний підрахунок посилань (ARC) у Xcode 4.2?


1
Нещодавно я знайшов статтю від Метта Галлоуей, яка досить глибоко розглядає Singletons як для 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;
}


1
@MakingScienceFictionFact, ви можете поглянути на це повідомлення
kervich

6
staticЗмінні @David, оголошені в методі / функції, такі ж, як і staticзмінна, оголошена поза методом / функцією, вони справедливі лише в межах цього методу / функції. Кожен окремий запуск +sharedInstanceметоду (навіть у різних потоках) буде "бачити" одну і ту ж sharedInstanceзмінну.
Нік Фордж

9
Що робити, якщо хтось дзвонить [[MyClass alloc] init]? Це створило б новий об’єкт. Як ми можемо цього уникнути (крім декларування статичного MyClass * sharedInstance = нуля поза методом).
Рікардо Санчес-Саес

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;
}

1
True / False: dispatch_once()Біт означає, що ви не отримаєте додаткових екземплярів, навіть у першому прикладі ...?
Олі

4
@Olie: помилково, оскільки клієнтський код може робити [[MyClass alloc] init]і обходити sharedInstanceдоступ. DongXu, ви повинні подивитися статтю Пітера Хосі про одиночку . Якщо ви збираєтесь переосмислити, allocWithZone:щоб запобігти створенню більшої кількості примірників, вам також слід замінити, initщоб запобігти повторній ініціалізації спільного екземпляра.
jscs

Гаразд, ось що я подумав, звідси і allocWithZone:версія. Дякую.
Олі

2
Це повністю розриває контракт на allocWithZone.
окулус

1
Singleton просто означає "лише один об'єкт у пам'яті в будь-який час", це одне, повторно ініціалізуватися - інша річ.
DongXu

5

Це версія для ARC та non-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

1
Чи не це призведе до c1того, що ви є екземпляром AAAсуперкласу? Вам потрібно зателефонувати +allocна self, а нема на super.
Нік Фордж

@NickForge superне означає об’єкт суперкласу. Ви не можете отримати об’єкт суперкласу, це просто означає маршрутизацію повідомлень до версії методу суперкласу. superдосі бали selfкласу. Якщо ви хочете отримати об’єкт суперкласу, вам потрібно отримати функції відображення часу виконання.
eonil

@NickForge І -allocWithZone:метод - просто проста ланцюг функції розподілу часу виконання, щоб запропонувати переважну точку. Отже, в кінцевому підсумку, selfпокажчик == об’єкт поточного класу буде переданий алокатору, і нарешті AAAекземпляр буде виділений.
eonil

ви маєте рацію, я забув тонкощі того, як superпрацює метод класів.
Нік Форж

Не забудьте скористатися #import <objc / objc-runtime.h>
Ryan Heitner

2

Прочитайте цю відповідь, а потім перейдіть і прочитайте іншу відповідь.

Спочатку ви повинні знати, що означає Singleton, і які її вимоги, якщо ви цього не розумієте, то не зрозумієте рішення - взагалі!

Щоб успішно створити Singleton, ви повинні зробити наступні 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

якщо вам потрібно швидко створити одиночку,

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.