ChatGPT解决这个技术问题 Extra ChatGPT

Objective-C 中的常量

我正在开发一个 Cocoa 应用程序,并且我使用常量 NSString 作为存储键名的方式来满足我的偏好。

我知道这是一个好主意,因为它可以在必要时轻松更改密钥。另外,这是整个“将数据与逻辑分开”的概念。

无论如何,有没有一种好方法可以为整个应用程序定义一次这些常量?

我确信有一种简单而智能的方法,但现在我的课程只是重新定义他们使用的课程。

OOP 是关于用你的逻辑对你的数据进行分组。你提出的只是一个好的编程实践,即让你的程序易于更改。

P
Paulo Mattos

您应该创建一个头文件,如:

// Constants.h
FOUNDATION_EXPORT NSString *const MyFirstConstant;
FOUNDATION_EXPORT NSString *const MySecondConstant;
//etc.

(如果您的代码不会在混合 C/C++ 环境或其他平台上使用,您可以使用 extern 而不是 FOUNDATION_EXPORT。)

您可以将此文件包含在每个使用常量的文件中或项目的预编译头文件中。

您可以在 .m 文件中定义这些常量,例如:

// Constants.m
NSString *const MyFirstConstant = @"FirstConstant";
NSString *const MySecondConstant = @"SecondConstant";

Constants.m 应添加到您的应用程序/框架的目标中,以便将其链接到最终产品。

使用字符串常量而不是 #define 的常量的优点是您可以使用指针比较 (stringInstance == MyFirstConstant) 测试相等性,这比字符串比较 ([stringInstance isEqualToString:MyFirstConstant]) 快得多(并且更易于阅读,IMO)。


对于整数常量,它是:extern int const MyFirstConstant = 1;
总的来说,这是一个很好的答案,但有一个明显的警告:您不想在 Objective-C 中使用 == 运算符测试字符串是否相等,因为它会测试内存地址。为此,请始终使用 -isEqualToString:。您可以通过比较 MyFirstConstant 和 [NSString stringWithFormat:MyFirstConstant] 轻松获得不同的实例。对你拥有的字符串实例不要做任何假设,即使是文字也是如此。 (无论如何,#define 是一个“预处理器指令”,并且在编译之前被替换,因此无论哪种方式,编译器最后都会看到一个字符串文字。)
在这种情况下,如果它真的被用作常量符号(即使用符号 MyFirstConstant 而不是包含 @"MyFirstConstant" 的字符串),则可以使用 == 来测试与常量的相等性。在这种情况下,可以使用整数而不是字符串(实际上,这就是您正在做的——将指针用作整数),但使用常量字符串会使调试稍微容易一些,因为常量的值具有人类可读的含义.
+1 表示“应将Constants.m 添加到您的应用程序/框架的目标中,以便将其链接到最终产品。”拯救了我的理智。 @amok,在 Constants.m 上执行“获取信息”并选择“目标”选项卡。确保检查了相关目标。
@Barry:在 Cocoa 中,我看到许多使用 copy 而不是 retain 定义其 NSString 属性的类。因此,它们可能(并且应该)持有 NSString* 常量的不同实例,直接内存地址比较将失败。此外,我认为 -isEqualToString: 的任何合理优化实现都会在进入字符比较的细节之前检查指针是否相等。
A
Andrew Grant

最简单的方法:

// Prefs.h
#define PREFS_MY_CONSTANT @"prefs_my_constant"

更好的方法:

// Prefs.h
extern NSString * const PREFS_MY_CONSTANT;

// Prefs.m
NSString * const PREFS_MY_CONSTANT = @"prefs_my_constant";

第二种方法的一个好处是更改常量的值不会导致整个程序的重建。


我认为您不应该更改常量的值。
Andrew 指的是在编码时更改常量的值,而不是在应用程序运行时更改。
extern NSString const * const MyConstant是否有任何附加价值,即使它成为一个指向一个常量对象的常量指针,而不仅仅是一个常量指针?
会发生什么,如果我在头文件中使用这个声明, static NSString * const kNSStringConst = @"const value";在 .h 和 .m 文件中不分别声明和初始化有什么区别?
@Dogweather - 只有编译器知道答案的地方。 IE,如果你想在关于菜单中包含哪个编译器用于编译应用程序的构建,你可以把它放在那里,因为编译后的代码无论如何都不知道。我想不出很多其他地方。宏当然不应该在很多地方使用。如果我有#define MY_CONST 5 和其他地方的#define MY_CONST_2 25 会怎样。结果是,当它尝试编译 5_2 时,你很可能会遇到编译器错误。不要将#define 用于常量。将 const 用于常量。
C
Community

还有一件事要提。如果你需要一个非全局常量,你应该使用 static 关键字。

例子

// In your *.m file
static NSString * const kNSStringConst = @"const value";

由于 static 关键字,此 const 在文件外不可见。

@QuinnTaylor 的小幅修正:静态变量在编译单元中可见。通常,这是一个单独的 .m 文件(如本例所示),但如果您在包含在其他地方的标头中声明它,它可能会咬您一口,因为编译后会出现链接器错误


次要更正:静态变量在编译单元中可见。通常,这是一个单独的 .m 文件(如本例所示),但如果您在包含在其他地方的标头中声明它,它可能会咬您一口,因为编译后会出现链接器错误。
如果我不使用 static 关键字,kNSStringConst 会在整个项目中可用吗?
好的,刚刚检查过...如果您关闭静态,Xcode 不会在其他文件中为其提供自动完成功能,但我尝试将相同的名称放在两个不同的地方,并重现了 Quinn 的链接器错误。
头文件中的静态不会给链接器带来问题。但是,每个包含头文件的编译单元都会获得自己的静态变量,因此如果您包含 100 个 .m 文件的头文件,您将获得其中的 100 个。
@kompozer 你把它放在 .m 文件的哪个部分?
P
Peter Mortensen

接受的(和正确的)答案说“您可以将此 [Constants.h] 文件...包含在项目的预编译头文件中。”

作为一个新手,我在没有进一步解释的情况下很难做到这一点——方法如下:在 YourAppNameHere-Prefix.pch 文件中(这是 Xcode 中预编译头文件的默认名称),在 { 1} 块。

#ifdef __OBJC__
  #import <UIKit/UIKit.h>
  #import <Foundation/Foundation.h>
  #import "Constants.h"
#endif

另请注意,除了接受的答案中描述的内容外,Constants.h 和 Constants.m 文件中绝对不应包含任何其他内容。 (没有接口或实现)。


我这样做了,但是有些文件在编译时抛出错误“使用未声明的标识符'CONSTANTSNAME'如果我在抛出错误的文件中包含constant.h,它可以工作,但这不是我想要做的。我已经清理了,关机xcode和构建仍然存在问题......有什么想法吗?
R
Regexident

我通常使用 Barry Wark 和 Rahul Gupta 发布的方式。

虽然,我不喜欢在 .h 和 .m 文件中重复相同的单词。请注意,在以下示例中,这两个文件中的行几乎相同:

// file.h
extern NSString* const MyConst;

//file.m
NSString* const MyConst = @"Lorem ipsum";

因此,我喜欢做的是使用一些 C 预处理器机器。让我通过例子来解释。

我有一个定义宏 STR_CONST(name, value) 的头文件:

// StringConsts.h
#ifdef SYNTHESIZE_CONSTS
# define STR_CONST(name, value) NSString* const name = @ value
#else
# define STR_CONST(name, value) extern NSString* const name
#endif

在我想要定义常量的 .h/.m 对中,我执行以下操作:

// myfile.h
#import <StringConsts.h>

STR_CONST(MyConst, "Lorem Ipsum");
STR_CONST(MyOtherConst, "Hello world");

// myfile.m
#define SYNTHESIZE_CONSTS
#import "myfile.h"

等等,我只有 .h 文件中有关常量的所有信息。


嗯,有一点需要注意的是,如果头文件被导入到预编译的头文件中,你不能像这样使用这种技术,因为它不会将 .h 文件加载到 .m 文件中,因为它已经被编译了。不过有一种方法 - 请参阅我的答案(因为我无法在评论中添加漂亮的代码。
我无法让这个工作。如果我将#define SYNTHESIZE_CONSTS 放在#import "myfile.h" 之前,它会在.h 和.m 中执行NSString*...(使用助手视图和预处理器进行检查)。它会引发重新定义错误。如果我把它放在 #import "myfile.h" 之后,它会在两个文件中执行 extern NSString*...。然后它抛出“未定义的符号”错误。
M
MaddTheSane

我自己有一个标题,专门用于声明用于首选项的常量 NSStrings,如下所示:

extern NSString * const PPRememberMusicList;
extern NSString * const PPLoadMusicAtListLoad;
extern NSString * const PPAfterPlayingMusic;
extern NSString * const PPGotoStartupAfterPlaying;

然后在随附的 .m 文件中声明它们:

NSString * const PPRememberMusicList = @"Remember Music List";
NSString * const PPLoadMusicAtListLoad = @"Load music when loading list";
NSString * const PPAfterPlayingMusic = @"After playing music";
NSString * const PPGotoStartupAfterPlaying = @"Go to startup pos. after playing";

这种方法对我很有帮助。

编辑:请注意,如果字符串在多个文件中使用,这效果最好。如果只有一个文件使用它,您可以在使用该字符串的 .m 文件中执行 #define kNSStringConstant @"Constant NSString"


S
Scott Little

对@Krizz 的建议稍作修改,以便将常量头文件包含在 PCH 中时它可以正常工作,这是相当正常的。由于原始文件已导入 PCH,因此不会将其重新加载到 .m 文件中,因此您不会得到任何符号,并且链接器也不满意。

但是,以下修改允许它工作。这有点令人费解,但它确实有效。

您将需要 3 个文件、具有常量定义的 .h 文件、.h 文件和 .m 文件,我将使用 ConstantList.hConstants.h 和 {6 }, 分别。 Constants.h 的内容很简单:

// Constants.h
#define STR_CONST(name, value) extern NSString* const name
#include "ConstantList.h"

Constants.m 文件如下所示:

// Constants.m
#ifdef STR_CONST
    #undef STR_CONST
#endif
#define STR_CONST(name, value) NSString* const name = @ value
#include "ConstantList.h"

最后,ConstantList.h 文件中包含实际声明,仅此而已:

// ConstantList.h
STR_CONST(kMyConstant, "Value");
…

有几点需要注意:

我必须在#undefing 之后重新定义.m 文件中的宏才能使用宏。我还必须使用#include 而不是#import 才能正常工作并避免编译器看到以前预编译的值。每当更改任何值时,这将需要重新编译您的 PCH(可能还有整个项目),如果它们像往常一样分开(和复制)则不是这种情况。

希望这对某人有帮助。


使用#include 为我解决了这个头痛问题。
与接受的答案相比,这是否有任何性能/记忆损失?
与接受的答案相比,在性能方面,没有。从编译器的角度来看,它实际上是完全相同的东西。你最终得到相同的声明。如果您将上面的 extern 替换为 FOUNDATION_EXPORT,它们将完全相同。
a
axel22
// Prefs.h
extern NSString * const RAHUL;

// Prefs.m
NSString * const RAHUL = @"rahul";

G
Grant Limberg

正如 Abizer 所说,您可以将其放入 PCH 文件中。另一种不太脏的方法是为所有密钥创建一个包含文件,然后将其包含在您使用密钥的文件中,或者将其包含在 PCH 中。将它们放在自己的包含文件中,这至少为您提供了一个查找和定义所有这些常量的地方。


F
Forge

如果您想要全局常量之类的东西;一种快速而肮脏的方法是将常量声明放入 pch 文件中。


编辑 .pch 通常不是最好的主意。您必须找到一个实际定义变量的位置,几乎总是一个 .m 文件,因此在匹配的 .h 文件中声明它更有意义。如果您在整个项目中都需要它们,那么创建 Constants.h/m 对的公认答案是一个很好的答案。我通常将常量放在尽可能低的层次结构中,这取决于它们将在哪里使用。
P
P.J.Radadiya

如果您喜欢命名空间常量,您可以利用 struct,Friday Q&A 2011-08-19: Namespaced Constants and Functions

// in the header
extern const struct MANotifyingArrayNotificationsStruct
{
    NSString *didAddObject;
    NSString *didChangeObject;
    NSString *didRemoveObject;
} MANotifyingArrayNotifications;

// in the implementation
const struct MANotifyingArrayNotificationsStruct MANotifyingArrayNotifications = {
    .didAddObject = @"didAddObject",
    .didChangeObject = @"didChangeObject",
    .didRemoveObject = @"didRemoveObject"
};

一件很棒的事情!但是在 ARC 下,您需要在结构声明中的所有变量前面加上 __unsafe_unretained 限定符才能使其正常工作。
P
Peter Mortensen

尝试使用类方法:

+(NSString*)theMainTitle
{
    return @"Hello World";
}

我有时会使用它。


类方法不是常量。它在运行时有成本,并且可能并不总是返回相同的对象(如果您以这种方式实现它会返回,但您不一定以这种方式实现它),这意味着您必须使用 isEqualToString: 进行比较,这是运行时的进一步成本。当你想要常量时,就创建常量。
@Peter Hosey,虽然您的评论是正确的,但我们在 Ruby 等“高级”语言中每个 LOC 一次或更多次的性能受到影响,而不用担心它。我并不是说你不对,只是评论不同“世界”中的标准有何不同。
对 Ruby 来说是真的。人们编码的大多数性能对于典型的应用程序来说都是不必要的。
H
Howard Lovatt

我使用单例类,这样我可以模拟该类并在必要时更改常量以进行测试。常量类如下所示:

#import <Foundation/Foundation.h>

@interface iCode_Framework : NSObject

@property (readonly, nonatomic) unsigned int iBufCapacity;
@property (readonly, nonatomic) unsigned int iPort;
@property (readonly, nonatomic) NSString * urlStr;

@end

#import "iCode_Framework.h"

static iCode_Framework * instance;

@implementation iCode_Framework

@dynamic iBufCapacity;
@dynamic iPort;
@dynamic urlStr;

- (unsigned int)iBufCapacity
{
    return 1024u;
};

- (unsigned int)iPort
{
    return 1978u;
};

- (NSString *)urlStr
{
    return @"localhost";
};

+ (void)initialize
{
    if (!instance) {
        instance = [[super allocWithZone:NULL] init];
    }
}

+ (id)allocWithZone:(NSZone * const)notUsed
{
    return instance;
}

@end

并且它是这样使用的(注意使用常量 c 的简写 - 它可以节省每次输入 [[Constants alloc] init] ):

#import "iCode_FrameworkTests.h"
#import "iCode_Framework.h"

static iCode_Framework * c; // Shorthand

@implementation iCode_FrameworkTests

+ (void)initialize
{
    c  = [[iCode_Framework alloc] init]; // Used like normal class; easy to mock!
}

- (void)testSingleton
{
    STAssertNotNil(c, nil);
    STAssertEqualObjects(c, [iCode_Framework alloc], nil);
    STAssertEquals(c.iBufCapacity, 1024u, nil);
}

@end

R
Renetik

如果您想从目标 c 调用类似 NSString.newLine; 的内容,并且希望它是静态常量,您可以在 swift 中创建类似的内容:

public extension NSString {
    @objc public static let newLine = "\n"
}

而且你有很好的可读常量定义,并且可以在你选择的类型中使用,同时限制到类型的上下文。