【问题标题】:Global Variables for Class Methods类方法的全局变量
【发布时间】:2023-03-18 07:55:02
【问题描述】:

背景

在 Cocoa 中,Apple 经常使用以下范例:

[NSApplication sharedApplication]
[NSNotificationCenter defaultNotificationCenter]
[NSGraphicsContext currentContext]
[NSCalendar currentCalendar]

等等。

他们偶尔也会使用我认为在处理大量代码时更清晰易读的范例。

NSApp //which maps to [NSApplication sharedApplication]

目标

我希望能够在我自己的类和其他类的扩展中使用这种全局变量。

MYClassInstance
NSDefaultNotificationCenter
NSCal /* or */ NSCurrentCalendar

等等。

“废话”方法

#define。简单的#define NSCal [NSCalendar currentCalendar],但我们现在都知道,宏是邪恶的(或者他们是这么说的),而且它似乎不是正确的 Cocoa 方法。

苹果的方法

我能找到的关于NSApp 的唯一来源是APPKIT_EXTERN id NSApp;,它不是完全可重用的代码。除非我弄错了,否则所有这些代码都将NSApp 定义为id 周围的世界。不幸的是没有帮助。

关闭,但不完全

在我的搜索中,我设法找到了几条关于“全局常量”的线索,但是是这样的:

extern NSString * const StringConstant;

不幸的是,仅限于编译时常量,并且无法映射到必要的类方法。

底线

我希望能够推出我自己的 NSApp 风格的全局变量,它映射到像 [NSNotificationCenter defaultNotificationCenter] 这样的类方法。这可能吗?如果是这样,我应该怎么做?

进一步尝试

我正在尝试通过以下方式专门实现框架单例:

MySingletons.h

//...
extern id NSNotifCenter;
//...

MySingletons.m

//...
+(void)initialize
{
    NSNotifCenter = [NSNotificationCenter defaultCenter];
}
//...

MyAppDelegate.m

//...
#import "MySingletons.h"
//...
//in applicationDidFinishLaunching:
    [MySingletons initialize];
    NSLog(@"%@", NSNotifCenter);
//...

但是,这会导致无法找到 _NSNotifCenter 符号的编译时错误。

目标!

我目前正在开发一个 Objective-C 类来封装我在这个问题中提到的一些框架单例。等我弄好这里把 GitHub 的信息加进去。

【问题讨论】:

  • 在发布我的答案之前我可能读得太快了。您是在谈论为您的自己的单例还是为框架单例执行此操作?
  • 最好两者都有。应该有什么特别的区别吗?
  • 嗯,您可以直接访问保存自己的单例的变量,但不能访问框架类的变量。将扩展我的答案。
  • 这句话:“映射到 [NSApplication sharedApplication]”是错误的,并引导你走上花园小路。正如您在“Apple 的方法”下指出的那样,NSApp 只是一个 ID。在您的代码中提及“NSApp”是调用 [NSApplication sharedApplication];只是访问一个全局变量。

标签: objective-c cocoa macros global-variables class-method


【解决方案1】:

这很有趣,我在另一个问题上just made this suggestion

您只需将保存单例实例的变量公开为全局本身。 NSApp 实际上并不是 映射sharedApplication 调用。这是一个普通的旧指针;它是在应用程序启动过程中设置的,以指向您将从该调用中返回的同一实例。

就像NSApp 一样,您可以为任何导入标头的文件声明变量:

extern MySingleton * MySingletonInstance;

在标题中(如果你愿意,你可以使用APPKIT_EXTERNthe docs indicate 它只是在 ObjC 中解析为 extern)。

在实现文件中定义变量。通常保存共享实例的变量被声明为static 以限制其与该文件的链接。如果您删除static,则该语句定义在标头中“重新声明”的存储。

然后,像以前一样使用它。唯一需要注意的是,您仍然必须在第一次使用全局之前调用单例设置方法[MySingleton sharedInstance],以确保它已初始化。 -applicationDidFinishLaunching: 可能是一个很好的候选人。

至于创建指向框架单例的指针,您可以将 [CocoaSingleton sharedInstance] 的结果存储在您喜欢的任何变量中:想要使用它的类中的 ivar、局部变量或您初始化的全局变量通过您编写的函数在程序的早期阶段。

问题是,这不能保证不会引起问题。除了NSApp 的情况(或者除非它被记录在某处),实际上并不能保证您从任何给定的sharedInstance 调用中返回的对象在调用堆栈结束后仍然有效、有效或有用.

这可能只是妄想症,但我建议不要这样做,除非你能在某处找到保证你感兴趣的假定单例总是返回相同的实例。否则,您可能会突然得到一个悬空的全局指针。

针对您的代码,您的标头中的声明不会创建变量。你仍然需要一个定义

// MySingletons.h
// Dear compiler, There exists a variable, NSNotifCenter, whose 
// storage is elsewhere. I want to use that variable in this file.
extern id NSNotifCenter;

// MySingletons.m
// Dear compiler, please create this variable, reserving memory
// as necessary.
id NSNotifCenter;

@implementation MySingletons

// Now use the variable.
// etc.

如果你正在创建一个单例,你可能想看看Apple's singleton documentation

【讨论】:

  • 我试了一下框架单例,但我遇到了一些错误。你能检查我的更新吗?
  • 当然。您需要在某处(MySingletons.m 的顶层)有一个id NSNotifCenter定义。我在帖子中添加了一些内容来解决这个问题。不过有两个问题:Cocoa 中的前缀用于命名空间——您不应该将此变量称为NS...。另外,我相当肯定你不应该直接打电话给+initialize。当您向该类的实例发送消息时,运行时会为您执行此操作。如果这个类本身是一个单例,只需使用你的访问方法,+sharedInstance 或任何它的名字。
  • 当从一个类访问extern 变量时,这是否会导致类被调用(从而调用initialize?)我想看看我是否不能使用initialize方法来设置所有变量,而无需进行特定的设置调用。
  • 不,这个变量访问与类没有任何实际关系。变量恰好在同一个文件中;它根本不是课程的一部分。
  • 更多关于为什么这个变量只能被定义为外部而不是静态的信息可以在here找到。
【解决方案2】:

这里的现有讨论非常有趣,以至于我做了一些研究,发现了一些我以前从未意识到的东西:我可以将我自己项目中的 #import 头文件放入项目的 .pch 文件中(预编译的头文件) .这个头文件自动对我的项目中的所有其他类文件可见我不费吹灰之力

所以这是我现在正在做的一个例子。在.pch 文件中,现有代码下方:

#import "MyIncludes.h"

MyIncludes.h中有两种东西,category和externs(后者按照Josh的建议):

extern NSString* EnglishHiddenKey;
extern NSString* IndexOfCurrentTermKey;

@interface UIColor (mycats)
+ (UIColor*) myGolden;
+ (UIColor*) myPaler;
@end

MyIncludes.m 中,我们提供定义以满足头文件中的所有声明。 externs 不必在任何类中定义:

#import "MyIncludes.h"

NSString* EnglishHiddenKey = @"englishHidden";
NSString* IndexOfCurrentTermKey = @"indexOfCurrentTerm";

@implementation UIColor (mycats)
+ (UIColor*) myGolden {
    return [self colorWithRed:1.000 green:0.894 blue:0.541 alpha:.900];
}
+ (UIColor*) myPaler {
    return [self colorWithRed:1.000 green:0.996 blue:0.901 alpha:1.000];
}
@end

除了关于使用pch 文件获得神奇的全局可见性的部分之外,这与 Josh 的建议并没有什么不同。我将其作为单独的答案(而不仅仅是评论)发布,因为它很长并且需要格式化,并且明确的代码可能会对某人有所帮助。

(请注意,没有内存管理,因为我使用的是 ARC。当然,extern 会泄漏,但它们应该会泄漏:只要应用程序运行,它们就需要存在.)

【讨论】:

  • +1 用于研究。不过,您对泄漏的担忧是没有根据的。仅当您丢失指向内存块的指针而不释放内存块时才会发生泄漏。在这种情况下,指针和内存/对象的生命周期是相同的。没有泄漏。 (此外,在文字 NSStrings 的特定情况下,从来没有任何实际担心泄漏,因为它们是不朽的。)
  • 在 ARC 下,分配保留。所以说NSString* EnglishHiddenKey = @"englishHidden" 会增加这个字符串的保留计数。但是 ARC 没有释放外部变量的机制,就像释放实例变量一样;所以实际上存在一种泄漏。但是,正如我所说,我不在乎,因为这不是实例而是类,类的生命周期就是应用程序的生命周期。
  • 您确定分配一个 literal 字符串会导致发送retain 吗?如果是这种情况,我会感到惊讶——retain 什么都不做,文字字符串的引用计数为UINT_MAX。这实际上不是泄漏,原因有很多:a) 引用的生命周期和对象本身是相同的,b) 内存不是在运行时分配的,而是在编译时分配的,c) 生命周期是应用程序的生命周期。只有在应用运行时无法释放分配的内存时,才会发生泄漏。这段代码的内存管理在ARC和MRR下都是正确的。
  • 这个例子中的字符串是文字字符串只是一个偶然的事实。我建议的技术适用于任何类型的对象。 OP 正在考虑在 ARC 下说类似UIApp = [UIApplication sharedApplication]; 的话,那个故事中有一个保留。文字字符串的内存管理是特殊的事实是一个红鲱鱼。你可能是对的,我稍微误用了“泄漏”这个词,但只是为了准确地说出你在说什么 - 它不是“担心这个”意义上的“泄漏”。
  • 此外,完全可以在实例中使用外部变量,在这种情况下,如果您不管理内存,ARC 下确实存在 泄漏。我想说的是,“是的,在 ARC 下管理外部变量可能很棘手,但在这种情况下,没有什么可担心的。”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-17
  • 2011-01-17
  • 2014-01-16
  • 2015-05-24
  • 1970-01-01
相关资源
最近更新 更多