+ (void) initialize未被调用(Objective C)

10

我的方法 + (void)initialized 没有被调用,我在 Objective C 中非常新手。这段代码来源于书本《iPhone 游戏开发》,我必须显式地调用该方法才能让它工作。.m 文件中的代码如下:

ResourceManager *g_ResManager;

@implementation ResourceManager

//initialize is called automatically before the class gets any other message, per from https://dev59.com/JHVC5IYBdhLWcg3w7Vxq
+ (void) initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        g_ResManager = [[ResourceManager alloc] init];
    }
}

...

@end

但是在 .h 文件中,对变量进行了外部声明:

extern ResourceManager *g_ResManager; //paul <3's camel caps, hungarian notation, and underscores.

@interface ResourceManager : NSObject {
   ...
}
...
@end

我尝试了各种方法(去掉了外部引用,在.m声明中加入static),但总是出现编译错误。上面的代码可以编译,但initialize方法从未被调用(我放了一个断点来查看)。

有什么线索吗?

3个回答

14
+initialize只有在你向类的一个实例发送了一些消息后才会被调用。你发送了消息吗?
可能的一个问题是你从代码的另一个部分向g_ResManager发送了一条消息?这不会起作用,因为:
  1. g_ResManager在启动时为nil。
  2. 你向g_ResManager发送了一条消息,但它是nil
  3. Objective-C运行时认为“向一个类发送一条消息”并不是看起来在源代码中的语法形式,而是真正的对象和发送的消息。
  4. 所以,在这种情况下,nil得到了消息,nil不是ResourceManager的实例,因此也没有调用+initialize
我建议将你的代码更改如下:首先,在 .m 文件中,
static ResourceManager *g_ResManager;

@implementation ResourceManager

//initialize is called automatically before the class gets any other message
+ (void) initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        g_ResManager = [[ResourceManager alloc] init];
    }
}
+(ResourceManager*)sharedResourceManager
{
     return g_ResManager;
}
...

@end

然后在 .h 文件中,我只需要写

@interface ResourceManager:NSObject {
...
}
+(ResourceManager*)sharedResourceManager
@end

那么你可以始终使用[ResourceManager sharedResourceManager]

实际上,正如Rob在评论中所说,你完全可以在这种情况下放弃+initialize。将.m文件更改为类似以下内容:

@implementation ResourceManager

+(ResourceManager*)sharedResourceManager
{
     static ResourceManager *g_ResManager=nil;
     if(!g_ResManager){
         g_ResManager=[[ResourceManager alloc] init];
     }
     return g_ResManager;
}
...

@end

这是我个人经常使用的习惯用语。但我提醒您,这不是完全线程安全的!只要在创建线程之前调用[ResourceManager sharedResourceManager]一次应该就没问题了,我几乎总是这样做的,但这是需要注意的一点。另一方面,上面使用+initialize版本应该是线程安全的,由于+initialize行为定义良好。请参见此博客文章中的讨论。


这正是发生的事情。非常感谢!!!我来自Java,在这些情况下总是会得到NullPointerException。 :D 再次感谢! - reinaldoluckman
很高兴知道它起作用了。不管怎样,这只是表明有太多由经验不足的人编写的糟糕的iPhone编程书籍。没有理智的Obj-C开发者会创建这样一个全局外部实例变量。使用“+sharedFoo”来返回共享实例是一种惯用法。 - Yuji
Yuji关于+initialize的工作原理是完全正确的,但这里没有必要使用+initialize。如果g_ResManager为nil,只需在+sharedResourceManager中初始化即可。在这种情况下,您甚至可以将g_ResManager设置为函数静态变量;不需要文件静态变量。 - Rob Napier
是的,Rob,你说得完全正确。我原本想使用尽可能多的原始帖子内容... 我会稍微编辑一下我的观点。 - Yuji
2
extern 实际上是旧版 C 语言的遗留物。在 Objective-C 中应该避免使用它,除非你必须与基于 C 的库进行接口交互。 - Walt Sellers
显示剩余2条评论

6
从NSObject的文档中可知:
运行时会在程序中仅发送一次initialize消息给每个类,正是在该类或任何继承自该类的类从程序内部第一次接收到消息之前。因此,如果未使用该类,则该方法可能永远不会被调用。运行时以线程安全的方式向类发送initialize消息。超类先于它们的子类接收到该消息。
只有在向一个类发送其第一条信息之前,才会调用+ initialize方法。在向该类发送消息之前,不会调用+ initialize方法。
例如:如果您调用[[MyObject alloc] init];则在向MyObject发送alloc消息之前,将在MyObject上调用+ initialize方法。

谢谢你的回答,Jasarien。 - reinaldoluckman

0
请注意,当您不自己分配对象时,应该保留它。例如:
+ (NSPredicate *)somePredicate
{
    static NSPredicate *predicate = nil;
    if (!predicate) {
        predicate = [NSPredicate predicateWithFormat:@"status == 2"];
        [predicate retain];
    }
    return predicate;
}

否则它就无法存活足够长的时间以便使用几次(指针仍然是!nil,但不再有效)。

这是正确的,但它不是问题的答案。 - benzado
好的,我应该把这个作为评论。抱歉。 - Dirk
@EricGoldberg 这个方法确实拥有保留值;它存储在静态变量中。这是正确的代码。 - Ian Henry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接