【问题标题】:Singleton with ARC带 ARC 的单例
【发布时间】:2011-11-03 15:34:14
【问题描述】:

我的问题如下:我有一个单例类型对象(我正在使用 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:&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 模型不会给出此错误????

【问题讨论】:

  • 请同时显示 .h 文件和方法声明
  • 这不是单例问题 - CocoaFu 是对的(但已删除答案):您的选择器缺少一个“t”。它被用作[self saveContex:。

标签: objective-c singleton automatic-ref-counting


【解决方案1】:

方法中的关键字self引用方法的所有者,对于实例方法是对象的实例,对于类方法是类。但是,saveContex 消息末尾缺少一个 t (saveContext)。

dispatch_once 单例

这是一个与 ARC 兼容的更好的单例习语:

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

与 Xcode 模板相同的代码

与带有占位符的 Xcode 模板相同的代码:

+ (<#class#> *)shared<#name#> {
    static dispatch_once_t onceToken;
    static <#class#> *shared<#name#> = nil;
    dispatch_once(&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(&pred, ^{
        shared = [[super alloc] initUniqueInstance];
    });
    return shared;
}

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

@end

警告:dispatch_once 不可重入

不要从dispatch_once 块内递归调用sharedInstance。

如果您从多个线程调用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(&token, ^{
    //  test();
    //});

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

    --count;
}

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

+初始化单例

使用+initialize 是另一种创建单例的惯用语。优点:它比dispatch_once 快几倍。缺点:+initialize 每个类调用一次,因此如果您将单例子类化,也会为每个父类创建一个实例。仅当您知道单例不会被子类化时才使用它。

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

【讨论】:

  • @jano 我的意思是你对自我感到困惑。 OP 的使用是正确的——你错了。尽管 OP 的代码一团糟,但 self (或 sharedInstance 的构造方式)没有问题。 [self saveContext:] 根本不调用实例方法。在 Class 方法中,“self”指的是 Class。
  • 是的,我的回答明显错误,我在您发表第一条评论后编辑了顶部。使用 dispatch_once_t 的单例实现更好,没有竞争条件。
  • 对单例实现使用共享实例无论如何都不是“更好”。我认为直接使用该类没有问题。如果为了实现真正的单例,任何一种方式都“更好”或“更差”,那么直接使用类肯定比使用共享实例“更好”。
  • 我不明白,抱歉。说得更好是指 dispatch_once 我的意思是它保证一次只有一个线程将执行块(尽管如果同一个线程从块内部再次调用它,它仍然会死锁)。
  • 这是单例模式的一个令人愉快的扩展。我不知道将方法标记为不可用的编译器扩展:我非常喜欢这个,而不是通常在覆盖 alloc/init/etc 时执行的所有保留/释放技巧。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多