只初始化一个类一次

Posted

技术标签:

【中文标题】只初始化一个类一次【英文标题】:Initialize a class only once 【发布时间】:2009-08-14 00:24:42 【问题描述】:

我有一个类,其中包含一些需要从另一个类调用的实例方法。我知道该怎么做 -

TimeFormatter *myTimeFormatter = [[TimeFormatter alloc] init];
[myTimeFormatter formatTime:time];

但是,我不想每次需要调用 TimeFormatter 的方法之一时都必须分配和初始化 TimeFormatter。 (我需要从另一个类的各种方法中调用TimeFormatter的方法)。

我试着放了

TimeFormatter *myTimeFormatter = [[TimeFormatter alloc] init];

“本身”,或者不在任何块中,但是当我编译时,我得到一个“初始化元素不是常量”错误。

非常感谢任何输入!

【问题讨论】:

【参考方案1】:

您可以使用单例模式。你可以阅读更多关于它的信息here。

具体来说,你会做这样的事情:

static TimeFormatter* gSharedTimeFormatter = nil;

@implementation TimeFormatter

+ (TimeFormatter*)sharedTimeFormatter 
  if (!gSharedTimeFormatter) 
    @synchronized(self) 
        if (!gSharedTimeFormatter) 
            gSharedTimeFormatter = [[TimeFormatter alloc] init];
        
    
  
  return gSharedTimeFormatter;


...

@end

请注意,我们检查变量是否为空,如果是,我们获取锁,然后再次检查。这样,我们只在分配路径上产生锁定成本,这在程序中只发生一次。这种模式称为double-checked locking。

【讨论】:

好答案。请注意,如果将整个 if 块移动到 +initialize 方法,则可以减少检查开销,因为第一次引用类时会调用 +initialize。但是检查仍然是一个好主意,因为引用任何子类默认情况下都会调用同一个父类的+initialize,除非子类提供自己的。如果您确实使用了这种方法,那么记录单例初始化的自动性质总是明智的。 感谢 Quinn - 我不知道 +initialize,但我在每种语言方面的经验告诉我,依赖静态构造函数(基本上是 +initialize)是一条不同的路。 【参考方案2】:

但是,我不想每次需要调用 TimeFormatter 的方法之一时都必须分配和初始化 TimeFormatter。 (我需要从另一个类的各种方法中调用TimeFormatter的方法)。

我认为有必要在这里澄清一些 OOP 术语。

您需要allocinit TimeFormatter 的原因是因为您的方法是实例方法。因为它们是实例方法,所以您需要一个实例,这就是 allocinit 提供的。然后你在实例 ([myTimeFormatter formatTimeString:…]) 上调用你的方法(发送消息到)。

允许实例的优点是您可以将状态和设置保留在每个实例、实例变量中,并使后者成为公开可见的属性。然后,您可以故意拥有多个实例,每个实例都有自己的设置,由使用该实例的任何人配置。

如果您不需要该功能,则无需创建这些实例方法。您可以使它们成为类方法甚至 C 函数,然后就不需要 TimeFormatter 实例。使用类方法,您可以直接向类发送消息 ([TimeFormatter formatTimeString:…])。

如果您确实希望在所有实例之间共享设置(并且您没有要保留的任何状态),那么您可以只拥有一个实例——一个单例,这是对的。

括号的原因是共享状态不好,特别是如果两个线程可能同时使用时间格式化程序。 (就此而言,您也可以这样说设置。如果一个线程想要秒而另一个不想要呢?如果一个线程想要 24 小时而另一个想要 12 小时怎么办?)最好让每个线程使用它的自己的时间格式化程序,这样他们就不会被对方的状态绊倒。

(顺便说一句,如果 TimeFormatter 是您班级的实际名称:您知道NSDateFormatter,对吗?它只允许您格式化/解析时间。)

【讨论】:

【参考方案3】:

这是一个 sharedMethod 的详细示例。归功于here

@implementation SearchData

@synthesize searchDict;
@synthesize searchArray;

- (id)init 
    if (self = [super init]) 
        NSString *path = [[NSBundle mainBundle] bundlePath];
        NSString *finalPath = [path stringByAppendingPathComponent:@"searches.plist"];
        searchDict = [[NSDictionary alloc] initWithContentsOfFile:finalPath];
        searchArray = [[searchDict allKeys] retain];
    

    return self;


- (void)dealloc 
    [searchDict release];
    [searchArray release];
    [super dealloc];


static SearchData *sharedSingleton = NULL;

+ (SearchData *)sharedSearchData 
    @synchronized(self) 
        if (sharedSingleton == NULL)
                sharedSingleton = [[self alloc] init];
       
    return(sharedSingleton);


@end

【讨论】:

@ITay 的回答比我的好。我会用他的。 这两个答案的组合是必需的,否则,在多线程环境中可能会泄漏内存。我会更新我的答案。 打字机比我快得多。好答案。 以防万一有人不清楚这一点 - 乔丹的回答是 100% 准确的。但是,每次访问单例时都会对锁产生巨大的影响。我修改后的解决方案只会在您第一次尝试访问它并需要分配时才会发生。【参考方案4】:

设置 Singleton 的一种非常好且简单的方法是使用 Matt Gallager 的 SYNTHESIZE_SINGLETON_FOR_CLASS。

【讨论】:

这种方法对于仅将单例变量作为现有类的一部分似乎有点过于严厉,但对于仅单例的类来说很好,所以我认为这值得一票。 【参考方案5】:

听起来你想让TimeFormatter 成为一个单例,其中只能创建一个实例。 Objective-C 并没有让这变得超级简单,但基本上你可以公开一个返回指向TimeFormatter 的指针的静态方法。该指针将在第一次分配和初始化,之后每次都可以使用相同的指针。 This question 有一些在 Objective-C 中创建单例的示例。

【讨论】:

【参考方案6】:

你想在类外声明你的变量?如果按照您想要的方式进行操作,您必须将其声明为静态,所以

静态 TimeFormatter *myFormatter=...

从类的名称来看,虽然我不明白你为什么要保留你的类的一个实例......你也可以使用如上所述的单例来做到这一点,也就是说,如果你想保留你的一个实例整个应用程序的类。

【讨论】:

static 不是必需的;它只是使变量对 TimeFormatter.m 私有,而不是在整个应用程序中可见。正如它所说,错误消息的原因是初始化程序,它是等号之后的消息表达式。由于错误消息是正确的对象,该表达式不是常量,因此它作为全局变量的初始化器无效(static 或其他)。

以上是关于只初始化一个类一次的主要内容,如果未能解决你的问题,请参考以下文章

KnockoutJs - 为啥初始化绑定处理程序只被调用一次?

只初始化一次 mongo 副本集

如何测试我的 NSNumberFormatter 只初始化一次?

某些只执行一次的初始化逻辑

Javafx只初始化一次控制器构造函数?

PHPUnit 运行程序