#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 .