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

Posted

技术标签:

【中文标题】如何实现与 ARC 兼容的 Objective-C 单例?【英文标题】:How do I implement an Objective-C singleton that is compatible with ARC? 【发布时间】:2011-11-26 00:06:16 【问题描述】:

如何转换(或创建)在 Xcode 4.2 中使用自动引用计数 (ARC) 时编译和行为正确的单例类?

【问题讨论】:

我最近发现 Matt Galloway 的一篇文章非常深入地介绍了用于 ARC 和手动内存管理环境的单例。 galloway.me.uk/tutorials/singleton-classes 【参考方案1】:

以您(应该)已经这样做的完全相同的方式:

+ (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;

【讨论】:

你只是不要做任何苹果过去在developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…推荐的内存管理骗术。 @MakingScienceFictionFact,你可能想看看this post @David static 在方法/函数中声明的变量与在方法/函数外声明的static 变量相同,它们仅在该方法/函数的范围内有效。通过+sharedInstance 方法的每次单独运行(即使在不同的线程上)都将“看到”相同的sharedInstance 变量。 如果有人调用 [[MyClass alloc] init] 怎么办?那将创建一个新对象。我们如何避免这种情况(除了在方法之外声明 static MyClass *sharedInstance = nil)。 如果另一个程序员在本应调用 sharedInstance 或类似名称的情况下搞砸了并调用了 init,这是他们的错误。颠覆语言的基本原理和基本契约以阻止其他人可能犯错误似乎是非常错误的。 boredzo.org/blog/archives/2009-06-17/doing-it-wrong 有更多讨论【参考方案2】:

如果您想根据需要创建其他实例。请执行以下操作:

+ (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;

【讨论】:

True/False:dispatch_once() 位意味着您不会获得额外的实例,即使在第一个示例中...? @Olie: False,因为客户端代码可以执行[[MyClass alloc] init] 并绕过sharedInstance 访问。东旭,你应该看看Peter Hosey's Singleton article。如果您要覆盖allocWithZone: 以防止创建更多实例,您还应该覆盖init 以防止重新初始化共享实例。 好吧,这就是我的想法,因此是allocWithZone: 版本。谢谢。 这完全违反了allocWithZone的约定。 单例只是表示“任何时候内存中只有一个对象”,这是一回事,重新初始化是另一回事。【参考方案3】:

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

我在 ios 6 中设置了一次断点,dispatch_once 出现在堆栈帧中。

【讨论】:

【参考方案4】:

This is a version for ARC and non-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

【讨论】:

【参考方案5】:

这是我在 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

【讨论】:

这不会导致c1 成为AAA 的超类的一个实例吗?您需要在self 上致电+alloc,而不是在super 上。 @NickForge super 并不意味着超类对象。你不能得到超类对象它只是意味着将消息路由到方法的超类版本。 super 仍然指向 self 类。如果要获取超类对象,则需要获取运行时反射函数。 @NickForge 和-allocWithZone: 方法只是运行时分配函数的简单链,以提供覆盖点。所以最终self指针==当前类对象将被传递给分配器,最后AAA实例将被分配。 你说得对,我忘记了 super 在类方法中的工作原理。 记得使用#import 【参考方案6】:
#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

希望上面的代码能帮上忙。

【讨论】:

【参考方案7】:

如果你需要在 swift 中创建单例,

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

【讨论】:

【参考方案8】:

阅读这个答案,然后去阅读另一个答案。

你必须先知道 Singleton 是什么意思,它的要求是什么,如果你不理解它,那么你就根本不理解解决方案!

要成功创建单例,您必须能够执行以下 3 项操作:

如果有race condition,那么我们绝不能允许同时创建多个 SharedInstance 实例! 记住并保留多次调用中的值。 只创建一次。通过控制入口点。

dispatch_once_t 仅允许分派其块一次,从而帮助您解决竞态条件

Static 帮助您“记住”它在任意数量的 调用。它是如何记忆的?它不允许再次创建具有您的 sharedInstance 确切名称的任何新实例,它只适用于最初创建的实例。

不使用在 sharedInstance 类上调用 alloc init(即我们仍然有 alloc init 方法,因为我们是 NSObject 子类,尽管我们不应该使用它们),我们通过使用+(instancetype)sharedInstance 来实现这一点,它仅限于启动一次,无论来自不同线程的多次尝试同时进行并记住它的值。

Cocoa 自带的一些最常见的系统单例是:

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

基本上任何需要具有集中效果的东西都需要遵循某种单例设计模式。

【讨论】:

【参考方案9】:

单例类:在任何情况下或通过任何方式,没有人可以创建多个类的对象。

+ (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];

【讨论】:

如果有人调用init,init会调用sharedInstance,sharedInstance会调用init,init再调用sharedInstance,然后crash!首先,这是一个无限递归循环。其次,调用dispatch_once的第二次迭代会崩溃,因为它不能从dispatch_once内部再次调用。【参考方案10】:

接受的答案有两个问题,可能与您的目的相关,也可能不相关。

    如果从 init 方法以某种方式再次调用 sharedInstance 方法(例如,因为其他使用单例的对象是从那里构造的),则会导致堆栈溢出。 对于类层次结构,只有一个单例(即:调用 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 *** 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;

【讨论】:

以上是关于如何实现与 ARC 兼容的 Objective-C 单例?的主要内容,如果未能解决你的问题,请参考以下文章

Objective-c的内存管理MRC与ARC

arc音频回传功能需要投影支持吗

内存管理-MRC与ARC详解

代理和 ARC,不兼容?

objective-c启用ARC时的内存管理 (循环引用)

ARC Objective-C 中的输出参数