【问题标题】:Constants in Objective-CObjective-C 中的常量
【发布时间】:2010-10-07 01:01:27
【问题描述】:

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

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

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

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

【问题讨论】:

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

标签: ios objective-c cocoa nsstring constants


【解决方案1】:

你应该创建一个像这样的头文件:

// 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'd 常量的优点是您可以使用指针比较 (stringInstance == MyFirstConstant) 来测试相等性,这比字符串比较 ([stringInstance isEqualToString:MyFirstConstant]) 快得多(并且更易于阅读,国际海事组织)。

【讨论】:

  • 对于一个整数常量会是: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: 的任何合理优化实现都会在进入字符比较的本质之前检查指针是否相等。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

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

    如果你喜欢命名空间常量,你可以利用结构,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 限定符才能使其正常工作。
    【解决方案5】:

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

    作为一个新手,如果没有进一步的解释,我很难做到这一点——方法如下:在 YourAppNameHere-Prefix.pch 文件(这是 Xcode 中预编译头文件的默认名称)中,导入您的 Constants.h #ifdef __OBJC__ 块内

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

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

    【讨论】:

    • 我这样做了,但有些文件在编译时抛出错误“使用未声明的标识符'CONSTANTSNAME'如果我在抛出错误的文件中包含常量.h,它可以工作,但这不是我想要的要做。我已经清理、关闭 xcode 和构建,但仍然存在问题......有什么想法吗?
    【解决方案6】:

    尝试使用类方法:

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

    我有时会使用它。

    【讨论】:

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

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

    例子

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

    由于static 关键字,这个常量在文件之外是不可见的。


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

    【讨论】:

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

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

    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"

    【讨论】:

      【解决方案9】:

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

      #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
      

      【讨论】:

        【解决方案10】:

        我一般使用 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 文件,因为它已经被编译。不过有一种方法 - 请参阅我的答案(因为我不能在 cmets 中放置好的代码。
        • 我无法正常工作。如果我将 #define SYNTHESIZE_CONSTS 放在 #import "myfile.h" 之前,它会在 .h 和 .m 中执行 NSString*... (使用助手视图和预处理器进行检查)。它会引发重新定义错误。如果我把它放在 #import "myfile.h" 之后,它会在两个文件中执行 extern NSString*...。然后它会抛出“未定义的符号”错误。
        【解决方案11】:

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

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

        你需要3个文件,.h文件有常量定义,.h文件和.m文件,我将使用ConstantList.hConstants.hConstants.m,分别。 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");
        …
        

        需要注意的几点:

        1. 我必须在.m 文件中重新定义宏 #undefing 之后才能使用宏。

        2. 我还必须使用 #include 而不是 #import 才能正常工作并避免编译器看到之前预编译的值。

        3. 这将需要在任何值发生更改时重新编译您的 PCH(可能还有整个项目),如果它们像往常一样分开(和复制),则情况并非如此。

        希望对某人有所帮助。

        【讨论】:

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

        【讨论】:

          【解决方案13】:

          最简单的方法:

          // 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 表示常量。
          【解决方案14】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-07-14
            • 2011-02-18
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多