Створіть синглтон, використовуючи dispatch_once GCD в Objective-C


341

Якщо ви можете націлити на iOS 4.0 або вище

Використовуючи GCD, це найкращий спосіб створити синглтон в Objective-C (безпека потоку)?

+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static id sharedInstance;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

2
Чи є спосіб заборонити користувачам класу викликати alloc / copy?
Nicolas Miari

3
dispatch_once_t та dispatch_once, здається, були введені в 4.0, а не в 4.1 (див .: developer.apple.com/library/ios/#documentation/Performance/… )
Бен Флінн

1
Цей метод стає проблематичним, якщо init вимагає використання одиночного об'єкта. Код Метта Галлахера працював на мене вже не раз. cocoawithlove.com/2008/11 / ...
Грегу

1
Я знаю її невдалий у цьому прикладі; але чому люди більше не використовують "нове". dispatch_once (& один раз, ^ {sharedInstance = [self new];} просто виглядає це трохи акуратніше. Еквівалентний alloc + init.
Кріс Хаттон

3
Обов’язково почніть використовувати тип повернення instancetype. Завершення коду набагато краще при використанні цього замість id.
Містер Роджерс

Відповіді:


215

Це цілком прийнятний і безпечний для потоків спосіб створити екземпляр свого класу. Технічно це не може бути "одиночним" (оскільки в ньому може бути лише 1 з цих об'єктів), але поки ви використовуєте лише [Foo sharedFoo]метод для доступу до об'єкта, це досить добре.


4
Як ви відпустите це?
samvermette

65
@samvermette ти цього не робиш. суть одинака полягає в тому, що він завжди буде існувати. таким чином, ви не відпускаєте його, і пам'ять відновлюється при виході процесу.
Дейв ДеЛонг

6
@Dave DeLong: На мою думку, цілість одинакових - це не впевненість у його безсмертї, а впевненість у тому, що у нас є один екземпляр. Що робити, якщо той сингтонний декремент є семафором? Не можна просто довільно сказати, що воно завжди буде.
jacekmigacz

4
@hooleyhoop Так, у своїй документації . "Якщо викликається одночасно з декількох потоків, ця функція чекає синхронно, поки блок не завершиться."
Кевін

3
@ WalterMartinVargas-Pena чітке посилання має статична змінна
Дейв ДеЛонг

36

екземпляр

instancetype- це лише одне з багатьох розширень мови до Objective-C, з кожним новим випуском додається більше.

Знай це, люби його.

І візьмемо це як приклад того, як звернення уваги на деталі низького рівня може дати вам уявлення про нові потужні способи перетворення Objective-C.

Посилайтесь тут: typetype


+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static id sharedInstance;

    dispatch_once(&once, ^
    {
        sharedInstance = [self new];
    });    
    return sharedInstance;
}

+ (Class*)sharedInstance
{
    static dispatch_once_t once;
    static Class *sharedInstance;

    dispatch_once(&once, ^
    {
        sharedInstance = [self new];
    });    
    return sharedInstance;
}

4
чудова порада, дякую! instancetype - це контекстне ключове слово, яке може використовуватися як тип результату для сигналізації про те, що метод повертає відповідний тип результату. ... За допомогою typetype компілятор правильно зробить тип.
Fattie

1
Мені не ясно, що тут означають два фрагменти, чи вони рівнозначні один одному? Один кращий за інший? Було б добре, якщо автор може додати трохи пояснень до цього.
galactica

33

MySingleton.h

@interface MySingleton : NSObject

+(instancetype)sharedInstance;

+(instancetype)alloc __attribute__((unavailable("alloc not available, call sharedInstance instead")));
-(instancetype)init __attribute__((unavailable("init not available, call sharedInstance instead")));
+(instancetype)new __attribute__((unavailable("new not available, call sharedInstance instead")));
-(instancetype)copy __attribute__((unavailable("copy not available, call sharedInstance instead")));

@end

MySingleton.m

@implementation MySingleton

+(instancetype)sharedInstance {
    static dispatch_once_t pred;
    static id shared = nil;
    dispatch_once(&pred, ^{
        shared = [[super alloc] initUniqueInstance];
    });
    return shared;
}

-(instancetype)initUniqueInstance {
    return [super init];
}

@end

Як init недоступний? Це не принаймні для одного init?
Мед

2
Синглтон повинен мати лише одну точку доступу. І цей пункт розділенийІнстанція. Якщо у файлі * .h у нас є метод init, ви можете створити інший екземпляр singleton. Це суперечить визначенню сингтона.
Сергій Петрук

1
@ asma22 __attribute __ ((недоступний ()) не доступний для використання цих методів. Якщо інший програміст хоче використовувати метод, позначений як недоступний, він отримує помилку
Сергій Петрук

1
Я повністю отримую, і я щасливий, що дізнався щось нове, нічого поганого у вашій відповіді, просто може бути трохи заплутаним для новачків ...
Мед,

1
Це працює лише для MySingleton, наприклад, MySingleton.mя дзвоню[super alloc]
Сергій Петрук

6

Ви можете уникнути, що клас буде виділено з перезаписом методу alloc.

@implementation MyClass

static BOOL useinside = NO;
static id _sharedObject = nil;


+(id) alloc {
    if (!useinside) {
        @throw [NSException exceptionWithName:@"Singleton Vialotaion" reason:@"You are violating the singleton class usage. Please call +sharedInstance method" userInfo:nil];
    }
    else {
        return [super alloc];
    }
}

+(id)sharedInstance
{
    static dispatch_once_t p = 0;
    dispatch_once(&p, ^{
        useinside = YES;
        _sharedObject = [[MyClass alloc] init];
        useinside = NO;
    });   
    // returns the same object each time
    return _sharedObject;
}

1
Це відповідає на моє запитання в коментарях вище. Мало того, що я настільки сильно
захищаю

5

Дейв правильно, це прекрасно. Ви можете перевірити документи Apple щодо створення синглтона для отримання порад щодо впровадження деяких інших методів, щоб гарантувати, що тільки один може бути створений, якщо класи обирають НЕ використовувати метод sharedFoo.


8
е-е ... це не найбільший приклад створення сингтона. Переосмислювати методи управління пам'яттю не потрібно.
Дейв ДеЛонг

19
Це повністю недійсне використання ARC.
logancautrell

Цитований документ з тих пір вийшов на пенсію. Крім того, відповіді, які є виключно посиланнями на зовнішній вміст, як правило, погані відповіді. Як мінімум уривок відповідних частин вашої відповіді. Не турбуйте тут, якщо ви не хочете, щоб старий спосіб був збережений для нащадків.
інструментберез

4

Якщо ви хочете переконатися, що [[MyClass alloc] init] повертає той самий об’єкт, що і sharedInstance (на мою думку, це не потрібно, але деякі люди хочуть цього), це можна зробити дуже легко і безпечно, використовуючи другу dispatch_once:

- (instancetype)init
{
    static dispatch_once_t once;
    static Class *sharedInstance;

    dispatch_once(&once, ^
    {
        // Your normal init code goes here. 
        sharedInstance = self;
    });

    return sharedInstance;
}

Це дозволяє будь-якій комбінації [[MyClass alloc] init] та [MyClass sharedInstance] повернути один і той же об'єкт; [MyClass sharedInstance] буде просто трохи ефективнішим. Як це працює: [MyClass sharedInstance] один раз зателефонує [[MyClass alloc] init]. Інший код може також називати його, будь-яку кількість разів. Перший виклик, який викликає init, зробить "звичайну" ініціалізацію та збереже єдиний об'єкт у методі init. Будь-які пізніші виклики до init повністю ігнорують те, що аллок повернувся та поверне той самий sharedInstance; результат алокації буде розміщений.

Метод + sharedInstance буде працювати як завжди. Якщо це не перший виклик, який викликав [[MyClass alloc] init], то результат init не є результатом виклику alloc, але це нормально.


2

Ви запитуєте, чи це "найкращий спосіб створити одиночку".

Кілька думок:

  1. По-перше, так, це безпечне для ниток рішення. Ця dispatch_onceзакономірність є сучасним безпечним для потоків способом генерування одинакових клавіш в Objective-C. Ніяких турбот там немає.

  2. Ти ж запитав, чи це "найкращий" спосіб це зробити. Слід, однак, визнати, що це instancetypeта [[self alloc] init]є потенційно оманливим при використанні спільно з одинаковими.

    Перевага instancetypeполягає в тому, що це однозначний спосіб заявити, що клас можна підкласифікувати, не вдаючись до типу id, як це потрібно було робити в минулому році.

    Але staticв цьому методі представлені проблеми підкласифікації. Що робити, якщо ImageCacheі BlobCacheодиночні одночасно були підкласами з Cacheсуперкласу, не застосовуючи власний sharedCacheметод?

    ImageCache *imageCache = [ImageCache sharedCache];  // fine
    BlobCache *blobCache = [BlobCache sharedCache];     // error; this will return the aforementioned ImageCache!!!

    Щоб це працювало, вам доведеться переконатись, що підкласи реалізують свій власний sharedInstance(або як ви його називаєте, для вашого конкретного класу) методу.

    Підсумок, ваш оригінал sharedInstance виглядає так, що він буде підтримувати підкласи, але це не буде. Якщо ви плануєте підтримувати підкласифікацію, принаймні включіть документацію, яка попереджає майбутніх розробників про те, що вони повинні перекрити цей метод.

  3. Для найкращої сумісності зі Swift ви, ймовірно, хочете визначити це властивістю, а не методом класу, наприклад:

    @interface Foo : NSObject
    @property (class, readonly, strong) Foo *sharedFoo;
    @end

    Тоді ви можете продовжити і написати Getter для цієї властивості (реалізація використовуватиме dispatch_onceзапропонований вами шаблон):

    + (Foo *)sharedFoo { ... }

    Перевага цього в тому, що якщо користувач Swift перейде на його використання, він зробить щось на зразок:

    let foo = Foo.shared

    Зауважте, що немає (), оскільки ми реалізували це як властивість. Починаючи з Swift 3, саме так, як правило, доступ до одиночних клавіш. Тож визначення його як властивості допомагає полегшити цю сумісність.

    Як осторонь, якщо ви подивитеся на те, як Apple визначає свої синглтони, це прийнята ними схема, наприклад, їх NSURLSessionсинглтон визначається наступним чином:

    @property (class, readonly, strong) NSURLSession *sharedSession;
  4. Іншим, дуже незначним питанням інтероперабельності Swift було ім'я синглтона. Найкраще, якщо ви можете включити ім’я типу, а не sharedInstance. Наприклад, якщо клас був Foo, ви можете визначити властивість singleton як sharedFoo. Або якби клас був DatabaseManager, ви можете зателефонувати у власність sharedManager. Тоді користувачі Swift могли:

    let foo = Foo.shared
    let manager = DatabaseManager.shared

    Зрозуміло, що якщо ви дійсно хочете використовувати sharedInstance, ви завжди можете оголосити ім'я Swift, якщо хочете:

    @property (class, readonly, strong) Foo* sharedInstance NS_SWIFT_NAME(shared);

    Зрозуміло, що при написанні коду Objective-C ми не повинні дозволяти інтероперабельності Swift переважати над іншими міркуваннями щодо дизайну, але все ж, якщо ми можемо написати код, який витончено підтримує обидві мови, це бажано.

  5. Я погоджуюся з іншими, хто зазначає, що якщо ви хочете, щоб це був справжній синглтон, де розробники не можуть / не повинні (випадково) інстанціювати власні екземпляри, unavailableкваліфікований користувач initі newє розсудливим.


0

Щоб створити безпечний одиночний потік, ви можете зробити так:

@interface SomeManager : NSObject
+ (id)sharedManager;
@end

/* thread safe */
@implementation SomeManager

static id sharedManager = nil;

+ (void)initialize {
    if (self == [SomeManager class]) {
        sharedManager = [[self alloc] init];
    }
}

+ (id)sharedManager {
    return sharedManager;
}
@end

і цей блог пояснює одиночні дуже добре одинаки в objc / какао


ви посилаєтесь на дуже стару статтю, в той час як ОП запитує характеристики щодо найсучаснішої реалізації.
vikingosegundo

1
Питання стосується конкретної реалізації. Ви просто опублікуєте ще одну реалізацію. Там ви навіть не намагаєтесь відповісти на запитання.
vikingosegundo

1
@vikingosegundo Запитуючи запитання про погоду, GCD - це найкращий спосіб створити безпечну однотонку для теми, моя відповідь дасть інший вибір. Чи не так?
Hancock_Xu

запитувач запитує, чи певна реалізація є безпечною для потоків. він не просить варіантів.
vikingosegundo

0
//Create Singleton  
  +( instancetype )defaultDBManager
    {

        static dispatch_once_t onceToken = 0;
        __strong static id _sharedObject = nil;

        dispatch_once(&onceToken, ^{
            _sharedObject = [[self alloc] init];
        });

        return _sharedObject;
    }


//In it method
-(instancetype)init
{
    self = [super init];
  if(self)
     {
   //Do your custom initialization
     }
     return self;
}

0
@interface className : NSObject{
+(className*)SingleTonShare;
}

@implementation className

+(className*)SingleTonShare{

static className* sharedObj = nil;
static dispatch_once_t once = 0;
dispatch_once(&once, ^{

if (sharedObj == nil){
    sharedObj = [[className alloc] init];
}
  });
     return sharedObj;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.