代码之家  ›  专栏  ›  技术社区  ›  ImHuntingWabbits

单例或类方法

  •  6
  • ImHuntingWabbits  · 技术社区  · 14 年前

    在阅读了对 question 关于Objective C中的singleton,似乎每个解决方案都在实例访问器中的线程方面做出了一些折衷。即

    @synchronized(self)
    {
        if (sharedInstance == nil)
                sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
    

    这本质上是单线程对单线程的访问,如果它是一个操作中经常使用的东西,那么它可能会导致线程不必要地争用。

    简单地使用类对象作为单例实例,并通过类方法公开功能,即。

    @interface MySingleton : NSObject {
    }
    
    + (void)doSomething;
    @end
    
    @implementation MySingleton    
    + (void)initialize {
       //do some setup if necessary
    }
    
    + (void)doSomething {
        //do something
    }
    @end
    

    这样我们就避免了每次要引用singleton对象时都要执行lock+检查,而且还可以避免将其存储在本地或方法ivar中。

    此方法还允许运行时保证在任何给定时间只有一个实例(类对象)存在于系统中。

    编辑

    这里不仅仅是线程,使用传统的单例,您通常会编写如下代码:

    MySingleton *instance = [MySingleton getSharedInstance];
    NSObject *someResult = [instance getResult];
    //or
    if (instance.someProperty) {
      //do something
    }
    

    但是,如果您的singleton是一个类实例,那么实际上您一直都不需要调用getSharedInstance。请考虑以下代码:

    NSObject *someResult = [MySingleton getResult];
    //or
    if ([MySingleton someProperty]) {
      //do something
    }
    

    我听说你必须把你的数据存储在文件本地静态变量,或全局变量(讨厌)。但它与传统的singleton并没有什么不同,只是您失去了Objective-C 2.0属性(相反,您必须使用传统的访问器方法)。

    对我来说,这里有一个关键的折衷方案似乎是一个胜利。在传统的单例中,如果您真的想把事情做好,那么您最终将重写-copyWithZone、+allocWithZone、-retain、-retainCount、-release和-autorelease。

    每次你想写一个简单的单例对象时,这看起来都是一件非常麻烦的工作(它们碰巧非常有用)。所以为什么不干脆用这个来代替它呢:

    @implementation MySingleton
    + (void)initialize {
        //do your setup
    }
    
    - (id)init {
        NSAssert(NO, @"You should read the documentation on singletons.");
    }
    @end
    

    它在代码方面要轻得多,除非你的消费者真的很狡猾,否则他们永远不会创建两个实例。

    已经说到重点了 我的问题是:

    使用类对象作为单例实例有什么缺点吗?

    在线程安全性、内存效率等方面,您似乎可以采取所有相同的步骤,而不必记住重写这么多方法和访问器,也不必在代码中浪费实例检查。

    4 回复  |  直到 7 年前
        1
  •  3
  •   Glyn Williams    13 年前

    这是我第一篇关于堆栈溢出的文章。。。(为愚蠢做准备)

    我认为有一个混合的解决方案可能是有用的。

    我想在不调用“getSharedInstance”的情况下设置并从单例类中获取(全局)值。我希望代码看起来像这样。。。

    frameRate = Singleton.frameRate;
    Singleton.frameRate = 42;
    

    为了实现这一点,我们需要存储在singleton中的每个变量都有一个getter和setter类方法。然后,类方法转到一个实例,将数据存储在ivar中。主程序无法直接访问该实例。

    getter看起来是这样的:

    + (int) frameRate
    {
        return [[Singleton instance] ivarFrameRate];
    }
    

    (丑陋的)实例调用隐藏在类代码中。

    通过在这里调用实例方法,类方法将在首次使用时自动实例化对象。一旦实例化了singleton,实例就按惯例存储ivar。在这里,我用“IVAR”前缀使IVAR显式。

    @property  int ivarFrameRate;
    

    @synthesize ivarFrameRate;
    

    这会自动创建传统的getter(和setter)方法来访问ivar。

    (编辑-下面是一个完整的示例)

    //  Singleton.h
    #import <Foundation/Foundation.h>
    @interface Singleton : NSObject
    {
    float ivarFrameRate
    }
    
    @property  float ivarFrameRate;
    
    - (id) init;
    + (Singleton *) instance;
    + (float) frameRate;
    + (void) setFrameRate:(float)fr;
    @end
    

    //  Singleton.m
    #import "Singleton.h"
    @implementation Singleton
    @synthesize ivarFrameRate;
    
    static Singleton* gInstance = NULL;
    
    + (Singleton*)instance
    {
        @synchronized(self)
        {
            if (gInstance == NULL)
            gInstance = [[self alloc] init];
        }
        return(gInstance);
    }
    
    
    - (id)init
    {
        self = [super init];
        return self;
    }
    
    + (float) frameRate
    {
        return [[Singleton instance] ivarFrameRate];
    }
    
    + (void) setFrameRate:(float)fr;
    {
        [[Singleton instance] setIvarFrameRate:fr];
    }
    
        2
  •  5
  •   Lily Ballard    14 年前

    对于iOS 4.0或更高版本,目前最好的解决方案是使用 dispatch_once ,如

    + (id)sharedInstance {
        static dispatch_once_t predicate;
        dispatch_once(&predicate, ^{
            sharedInstance = [[MyClass alloc] init];
        });
        return sharedInstance;
    }
    

    您可能还需要考虑使用单个分派队列来序列化对类内部的访问。如果所有的公共方法只是在同一个调度队列上运行一个块,那么就不必担心并发问题。

        3
  •  0
  •   slycrel    14 年前

    这很好,但仍然只是改变你的环境,而不是解决你的问题。除非你没有任何实际的数据绑定到你的单例,在这种情况下,这将工作得很好。无论何时访问中心数据,都需要使其具有线程安全性。

    另外,如果没有某种iVar,我不知道如何直接在类中存储数据。

    在上面的示例中,我将以这种方式进行编码,得到与您建议的结果相同的结果,并且只有在创建/重新创建单例时才能获得性能上的影响:

    if (sharedInstance)
         return sharedInstance;
    
    @synchronized(self)
    {
        if (sharedInstance == nil)
                sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
    

    请记住,无论哪种方式,如果您访问的数据可能在不同的线程上发生更改,那么无论如何,您都必须使该代码线程安全,要么进行非常仔细的规划,要么使用代码确保没有问题。我推荐一种混合,但如果有疑问的话,最好是后者。=)

        4
  •  0
  •   ughoavgfhw    14 年前

    如果使用类作为单例,存储数据的唯一方法是使用静态文件变量和全局变量。如果你打算做一个你不打算实例化的类,你也可以使用标准的C函数:

    void doSomething(void);
    
    void doSomething() {
        //do something
    }