如何实现与ARC兼容的Objective-C单例?


Answers:


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 2012年

6
static在方法/函数内声明的@David 变量与在方法/函数static外声明的变量相同,仅在该方法/函数的范围内有效。方法中的每个单独运行+sharedInstance(即使在不同的线程上)也将“看到”相同的sharedInstance变量。
尼克·福奇

9
如果有人调用[[MyClass alloc] init]怎么办?那将创建一个新对象。我们如何避免这种情况(除了在方法之外声明静态MyClass * sharedInstance = nil之外)。
里卡多·桑切斯·塞兹

2
如果另一个程序员搞砸了,并在应该调用sharedInstance或类似实例时调用了init,那是他们的错误。破坏语言的基础知识和基本契约,以阻止其他人可能犯错,这似乎是错误的。在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
对/错:该dispatch_once()位表示即使在第一个示例中,您也不会获得其他实例...?
奥利(Olie)

4
@Olie:错误,因为客户端代码可以执行[[MyClass alloc] init]并绕过sharedInstance访问。DongXu,您应该看一下Peter Hosey的Singleton文章。如果要覆盖allocWithZone:以防止创建更多实例,则还应覆盖init以防止共享实例被重新初始化。
jscs

好的,这就是我的想法,因此是allocWithZone:版本。谢谢。
奥利

2
这完全破坏了allocWithZone的契约。
occulus

1
单例只是意味着“任何时候在内存中只有一个对象”,这是一回事,重新初始化是另一回事。
东旭

5

这是ARC和非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的超类的实例?你需要调用+allocself,而不是super
Nick Forge

@NickForge super并不意味着超类对象。您无法获得超类对象,这仅意味着将消息路由到方法的超类版本。superself分班。如果要获取超类对象,则需要获取运行时反射函数。
2013年

@NickForge And -allocWithZone:方法只是运行时分配函数的简单链,以提供覆盖点。因此,最终,将self指针==当前类对象传递给分配器,最后AAA将分配实例。
2013年

没错,我忘记了super类方法的工作原理。
尼克·福奇

切记使用#import <objc / objc-runtime.h>
Ryan Heitner

2

阅读此答案,然后阅读其他答案。

首先,您必须了解Singleton的含义以及它的要求,如果您不了解,那就根本不了解解决方案了!

要成功创建Singleton,您必须能够执行以下3个操作:

  • 如果存在争用情况,那么我们一定不允许同时创建您的SharedInstance的多个实例!
  • 记住并在多次调用中保留值。
  • 仅创建一次。通过控制入口点。

dispatch_once_t通过仅允许调度其块一次来帮助您解决竞争状况

Static帮助您在任何数量的调用中“记住”其值。还记得什么?它不允许再次使用您的sharedInstance的确切名称来创建任何新实例,而只能与最初创建的实例一起使用。

在我们的sharedInstance类上不使用调用alloc init(即,alloc init由于我们是NSObject子类,我们仍然有方法,尽管我们不应该使用它们),我们可以通过使用来实现此目的+(instancetype)sharedInstance,该方法仅限于一次启动,而不管来自不同线程的多次尝试同时记住它的价值。

可可本身随附的一些最常见的系统Singletons是:

  • [UIApplication sharedApplication]
  • [NSUserDefaults standardUserDefaults]
  • [NSFileManager defaultManager]
  • [NSBundle mainBundle]
  • [NSOperations mainQueue]
  • [NSNotificationCenter defaultCenter]

基本上,任何需要集中作用的东西都需要遵循某种Singleton设计模式。


1

另外,Objective-C为NSObject及其所有子类提供+(void)initialize方法。它总是在类的任何方法之前调用。

我在iOS 6中一次设置了一个断点,然后dispatch_once出现在堆栈框架中。


0

单例类:在任何情况下或以任何方式,任何人都不能创建一个以上的类对象。

+ (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内部再次调用它。
Chuck Krutsinger

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.