Одноэлементный с ARC

#objective-c #singleton #automatic-ref-counting

#objective-c #одноэлементный #автоматический подсчет ссылок

Вопрос:

Мой вопрос заключается в следующем: у меня есть объект одноэлементного типа (я использую ARC), который содержит этот код в файле реализации

  (id)sharedInstance 
{
    static DataManager *sharedInstance;
    if (sharedInstance == nil) {
        sharedInstance = [[DataManager alloc] init];
    }
    return sharedInstance;
}

 (NSManagedObjectContext *)getManagedContext
{
    AppDelegate *applicationDelegate =(AppDelegate *)[[UIApplication sharedApplication] delegate];
    return [applicationDelegate managedObjectContext];
}

 (void)saveContext:(NSManagedObjectContext *)context
{
    NSError *error;
    if (![context save:amp;error]) {
        NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
    }
}

#pragma mark - Data management methods

 (void)addPersonWithName:(NSString *)name andPicture:(UIImage *)picture
{
    NSManagedObjectContext *context = [self getManagedContext]; //no problem here
    //some code 
    [self saveContex:context]; // no known class method for selector saveContext:
}
  

Почему это так? Метод объявлен в файле .h с помощью … getManagedContext модель не выдает эту ошибку????

Комментарии:

1. Пожалуйста, покажите файл .h также с объявлениями методов

2. Это не проблема с синглтоном — CocoaFu все правильно понял (но удалил ответ): в вашем селекторе отсутствует буква «t». он используется как [self saveContex: .

Ответ №1:

Ключевое слово self внутри метода ссылается на владельца метода, который является экземпляром объекта для методов экземпляра и классом для методов класса. Однако в сообщении saveContex отсутствует буква t в конце ( saveContext ).

dispatch_once одноэлементный

И вот лучшая одноэлементная идиома, совместимая с ARC:

  (MySingleton *)sharedInstance {
    static dispatch_once_t pred;
    static MySingleton *shared = nil;
    dispatch_once(amp;pred, ^{
        shared = [[MySingleton alloc] init];
    });
    return shared;
}
  

Тот же код, что и шаблон Xcode

Тот же код, что и в шаблоне Xcode с заполнителями:

   (<#class#> *)shared<#name#> {
    static dispatch_once_t onceToken;
    static <#class#> *shared<#name#> = nil;
    dispatch_once(amp;onceToken, ^{
        shared<#name#> = <#initializer#>;
    });
    return shared<#name#>;
}
  

Тот же код отключен alloc / init / new

Хотите подсказать пользователям, что они должны вызывать sharedInstance вместо alloc/init/new ? Вы можете отключить методы с недоступным атрибутом. Это вызовет ошибку компилятора, если какой-либо из этих методов вызывается в классе.

 #import <Foundation/Foundation.h>

@interface MySingleton : NSObject

 (instancetype) sharedInstance;

// clue for improper use (produces compile time error)
 (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")));

@end

#import "MySingleton.h"

@implementation MySingleton

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

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

@end
  

Предупреждение: dispatch_once не является реентерабельным

Не выполняйте рекурсивный вызов sharedInstance из внутри dispatch_once блока.

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

 #import <Foundation/Foundation.h>

static NSRecursiveLock *_lock = nil;
// constructor = run before main. used = emit code even if the function is not referenced.
// See http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html
static void runBeforeMain(void) __attribute__ ((constructor, used));
static void runBeforeMain(void) {
    _lock = [NSRecursiveLock new];
}

static void test(void) 
{
    static NSUInteger count = 0;
    NSLog(@"iteration #%lu",   count);

    // WRONG: deadlock!
    //static dispatch_once_t token;
    //dispatch_once(amp;token, ^{
    //  test();
    //});

    // OK
    [_lock lock];
    test();
    [_lock unlock];

    --count;
}

int main(int argc, char **argv) {
    @autoreleasepool {
        test();
    }
    return EXIT_SUCCESS;
}
  

инициализировать синглтон

Использование initialize — это альтернативная идиома для создания синглтона. Плюсы: это в несколько раз быстрее, чем dispatch_once . Недостатки: initialize вызывается один раз для каждого класса, поэтому, если вы создаете подкласс singleton, экземпляр будет создан и для каждого родительского класса. Используйте его, только если вы знаете, что синглтон не будет подклассом.

 static id sharedInstance;

  (void) initialize {
    // subclassing would result in an instance per class, probably not what we want
    NSAssert([MySingleton class] == self, @"Subclassing is not welcome");
    sharedInstance = [[super alloc] initUniqueInstance];
}

 (instancetype) sharedInstance {
    return sharedInstance;
}
  

Комментарии:

1. @jano я имею в виду, что ты запутался в себе. Использование OP правильно — вы ошибаетесь. Хотя код OP представляет собой беспорядок, проблем с self (или способом построения sharedInstance) нет. [self saveContext:] вообще не вызывает метод экземпляра. В методе класса ‘self’ ссылается на класс.

2. Да, мой ответ был явно неправильным, я отредактировал начало после вашего первого комментария. Одноэлементная реализация лучше с dispatch_once_t, без условий гонки.

3. Использование общего экземпляра для одноэлементной реализации никоим образом не «лучше». Я не вижу проблем в непосредственном использовании класса. Если любой из способов является «лучшим» или «худшим» для реализации истинного синглтона, прямое использование класса определенно «лучше», чем использование общего экземпляра.

4. Я не понял, что извините. Говоря лучше, ссылаясь на dispatch_once, я имею в виду, что это гарантирует, что только один поток будет выполнять блок одновременно (хотя он все равно может зайти в тупик, если тот же поток снова вызовет его изнутри блока).

5. Это восхитительное расширение одноэлементного шаблона. Я не знал о расширениях компилятора, чтобы пометить методы как недоступные: я очень предпочитаю это всем трюкам сохранения / освобождения, которые обычно выполняются при переопределении alloc / init / etc .