有没有办法在Objective-C中使用类型安全的常量?

时间:2015-06-15 19:40:44

标签: objective-c oop enums constants

我正在查看一个充满

的代码库
NSString *const kTabChart = @"Charts";
NSString *const kTabNews = @"News";

然后

setSelectedTab:(NSString *)title;
...
someThingElse:(NSString *)title;

所以这些弱类型的NSString在代码中走得很远,这只会激怒我的眼睛。枚举在某种程度上会更好,但是枚举不会以编程方式提供名称,我不想在同一个枚举中定义来自不同视图的所有不相关的选项卡名称{}

我想知道是否有更好的方法?我正在梦想一种方法,使它像

@interface PageTitle:NSSting;
PageTitle kTabChart = /some kind of initializer with @"Chart"/;
PageTitle kTabNews = /some kind of initializer with @"News"/;

我怀疑这对整个“非编译时常量”约束不会很好,但我想知道是否有窍门/模式/黑客来定义我自己的类类型的常量。

2 个答案:

答案 0 :(得分:0)

当然,只要想想子类化。首先是我们的类,它是NSString的子类:

@interface StringConstants : NSString

extern StringConstants * const kOptionApple;
extern StringConstants * const kOptionBlackberry;

@end

所以我们为它定义了StringConstants和几个全局常量。要在没有任何警告的情况下实现该类,只需要进行一些转换:

@implementation StringConstants

StringConstants * const kOptionApple = (StringConstants *)@"Apple";
StringConstants * const kOptionBlackberry = (StringConstants *)@"Blackberry";

@end

还有我们的常量。让我们测试一下:

- (void) printMe:(StringConstants *)string
{
   NSLog(@"string: %@", string);
}

- (void) test
{
   [self printMe:kOptionApple]; // Code completion offers the constants
   [self printMe:@"Rhubarb"];   // Warning: Incompatible pointer types
   [self printMe:(StringConstants *)@"Custard"]; // OK
}

您只会收到警告,代码将会运行,与其他类似的类型错误一样。

你当然可以重复这个模式并制作一个"类"每组字符串。

HTH

附录:它是安全的(现在相信我)和弱的恩赐

上述代码基本上是危险的,并且的评论中提出了关注。但是在一般情况下,所提出的问题是有效的,这里设计是安全的。

  

注意:这是直接在SO中输入的。请原谅不可避免的拼写&语法错误,可能缺乏良好的表达,定义明确的解释性弧,缺失位,冗余位等。

首先,让我们将缺少的注释添加到上面的代码中,从实现开始:

// The following *downcasts* strings to be StringConstants, code that
// does this should only appear in this implementation file. Use in
// other circumstances would effectively increase the number of "enum"
// values in the set, which rather defeats the purpose of this class!
//
// In general downcasting should only be performed after type checks to
// make sure it is safe. In this particular case *by design* it is safe.

StringConstants * const kOptionApple = (StringConstants *)@"Apple";

这里有两个不同的问题

  1. 它是否安全 - 是设计,相信我(现在);和
  2. 添加额外的" enum"值
  3. 测试代码中第二个缺失的评论涵盖了第二个:

    [self printMe:(StringConstants *)@"Custard"]; // OK :-( - go ahead, graft
                                                  // in a new value and shoot
                                                  // yourself in the foot if
                                                  // you must ;-)
    

    enum

    首先处理第二个问题,不出所料这个" enum"不防弹 - 您可以动态添加其他值。为什么不出奇怪?因为你也可以在(Objective-)C中完成它,不仅语言不是强类型,enum类型是最弱的。考虑:

    typedef enum { kApple, kBlackberry } PieOptions;
    

    有多少PieOptions的有效值?使用Xcode / Clang:2 ^ 32, 2.以下内容完全有效:

    PieOptions po = (PieOptions)42;
    

    现在虽然您不应该编写这样明显错误的代码,但是需要在整数和enum值之间进行转换 - 例如当存储" enum" UI控件的标记字段中的值 - 因此存在错误的空间。 C风格的枚举必须与纪律一起使用,并且以这种方式使用它可以很好地帮助编程正确性和可读性。

    同样地,StringConstants必须与纪律一起使用,没有任何投射任意字符串 - 相当于上面的例子 - 并且遵守规则它们与标准枚举具有相似的优点和缺点。

      

    简单的规则是不将任意字符串转换为StringConstants; StringConstants实施本身允许的内容;这种类型为您提供了一个完全安全的字符串值枚举"如果使用不正确,请使用编译时警告。

    如果你相信我,你现在可以停止阅读......

    附录:深入挖掘(只是好奇或我们不相信你)

    要理解为什么StringConstants是完全安全的(即使添加其他值并非真的不安全,尽管它可能会导致程序逻辑失败)我们会经历一些关于面向对象编程,动态类型和Objective-C的本质。以下内容对于理解StringConstants为何是安全的非常必要,但对于一个有探究性思维的人来说,不是吗?

    对象引用强制转换在运行时不做任何事情

    从一个对象引用类型到另一个对象引用类型的强制转换是一个编译时语句,该引用应被视为对象目标类型的对象。它在运行时对实际引用的对象没有影响 - 该对象具有实际类型且不会更改。在面向对象的模型 upcasting 中,从一个类到一个超类,总是安全的,向下转换,反方向,可能不会< / em>( 不是)是安全的。因此,在可能不安全的情况下,应通过测试来保护向下转型。例如:

    NSArray *one = @[ @{ @"this": @"is", @"a" : @"dictionary" } ];
    

    代码:

    NSUInteger len = [one.firstObject length]; // error, meant count, but NO compiler help at all -> runtime error
    

    将在运行时失败。 firstObject的结果类型是id,这意味着任何对象类型,编译器将允许您调用类型为id的引用上的任何方法。这里的错误是不检查数组边界,并且检索的引用实际上是字典。更加防弹的方法是:

    if (one.count > 0)
    {
       id first = one.firstObject;
       if ([first isKindOfClass:[NSDictionary class]])
       {
          NSDictionary *firstDict = first; // *downcast* to improve compile time checking
          NSLog(@"The count of the first item is %lu", firstDict.count);
       }
       else
          NSLog(@"The first item is not a dictionary");
    }
    else
       NSLog(@"The array his empty");
    

    (不可见)强制转换非常安全,受isKindOf:测试保护。在上面的代码片段中意外键入firstDict.length收到编译时错误。

    但是,如果转发可能无效,只需要无效就不需要进行测试,只需执行此操作。

    为什么可以将引用类型的任何方法调用为id

    这是Objective-C的动态运行时消息查找发挥作用的地方。 编译器在编译时尽力检查类型错误。然后在运行时进行另一次检查 - 引用的对象是否支持被调用的方法?如果它没有生成运行时错误 - 与上面的length示例一样。当对象引用被输入为id时,这是一条指令,编译器根本不执行编译时检查,并将其全部留给运行时检查。

    运行时检查不检查引用对象的类型,而是检查它是否支持所请求的方法,这导致我们...

    Ducks,NSProxy,继承等。人

    鸭子?!

    在动态类型中有一句话:

      

    如果看起来像鸭子,像鸭子一样游泳,像鸭子一样呱呱叫,那就是鸭子。

    在Objective-C术语中,这意味着在运行时如果引用的对象支持类型A的方法集,则它有效地类型A的对象,而不管它的实际类型是什么。< / p>

    此功能在Objective-C的许多地方使用,一个值得注意的例子是NSProxy

      

    NSProxy是一个抽象超类,它定义了对象的API,这些对象充当其他对象或不存在的对象的替身。通常,将代理的消息转发到真实对象或使代理加载(或转换为自身)真实对象。 NSProxy的子类可用于实现透明的分布式消息传递(例如,NSDistantObject)或用于创建昂贵的对象的惰性实例化。

    使用NSProxy你可能会认为你有一个NSDictionary - 一些&#34;看起来,游泳和嘎嘎叫&#34;像一本字典 - 但实际上你根本没有一本字典。重点是:

    1. 没关系;和
    2. 完全安全(当然是模数编码错误)
    3. 你可以看到这种能力将一个对象替换为另一个对象作为继承的概括 - 后者总是可以使用子类实例来代替超类的一个,前者可以使用任何对象代替另一个对象只要它&#34;看起来,游泳和嘎嘎叫&#34;就像它所代表的对象一样。

      我们实际上已经超出了我们的需要,鸭子并不是真正需要了解StringConstants,所以让我们开始吧:

      字符串何时是NSString的实例?

      可能从不 ......

      NSString类集群实现 - 这是一组类,它们都响应NSString所做的同一组方法,即它们都像{{{}}一样嘎嘎叫1}}。现在这些类可能NSString的子类,但在Objective-C中,实际上并不需要它们。

      此外,如果您认为自己拥有NSString的实例,那么您实际上可能会有NSString的实例... 但这无关紧要。(这可能会影响性能,但它不会影响安全性或正确性。)

      NSProxyStringConstants的子类,所以它肯定是NSString,除了NSString个实例可能不存在 - 每个字符串实际上都是集群中某个其他类的实例,它本身可能是也可能不是NSString的子类。 但没关系!

      只要像NSString这样的StringConstants呱呱的实例应该 NSString s - 并且我们在实现中定义的所有实例都可以因为他们字符串(某种类型,可能是NSString)。

      这给我们留下的问题是__NSCFConstantString常数声音的定义?这是同一个问题:

      什么时候向下传播已经安全?

      首先是一个不是时的例子:

      如果您的引用类型为StringConstants,那么不知道是安全将其强制转换为NSDictionary * 而不首先测试是否引用是一个可变字典。

      编译器总是允许你进行转换,然后你可以在编译时调用mutating方法而不会出错,但是在运行时会出现错误。在这种情况下,您必须在投射前进行测试。

        

      请注意,标准测试NSMutableDictionary *实际上是保守的,因为所有这些都是鸭子。实际上,您可能会引用一个像isKindOf:那样嘎嘎叫但不是它的实例的对象 - 所以转换会很安全,但测试会失败。

      是什么让这个演员一般不安全?

      简单,不知道引用对象是否响应NSMutableDictionary所做的方法......

      Ergo,如果你知道引用必须响应你所投射的类型的所有方法,那么强制转换始终是安全的并且不需要测试。

      那么你如何知道引用必须响应目标类型的所有方法?

      有一种情况很简单:如果您的引用类型为NSMutableDictionary,则可以安全地将其引用到T类型的引用,而不进行任何检查,如果:

      1. SS的子类 - 所以它像T一样嘎嘎作响;
      2. T不向S添加实例状态(变量);
      3. T不向S添加实例行为(新方法,覆盖等);
      4. T会覆盖无课堂行为
      5. S 可以添加类新类方法(不是覆盖)和全局变量/常量而不违反这些要求。

        换句话说,S定义为:

        S

        我们做到了......

        或者我们,还有人还在读书吗?

        1. @interface S : T // zero or more new class methods // zero or more global variables or constants @end @implementation S // implementation of any added class methods, etc. @end 按设计构造的,以便可以将字符串实例强制转换为它。这应该在实现中完成,潜入额外的&#34; enum&#34;其他地方的常数违背了本课程的目的。
        2. 安全,实际上它甚至不是吓人的: - )
        3. 没有创建StringConstants的实际实例,每个常量都是在编译时伪装成StringConstants实例时安全的某个字符串类的实例。
        4. 它提供编译时检查字符串常量是否来自一组预先确定的值,它实际上是一个&#34;字符串值枚举&#34;。
        5. 又一个附录:执行纪律

          您无法完全自动执行在Objective-C中安全编码所需的纪律。

          特别是你不能让编译器阻止程序员将任意整数值转换为StringConstants类型。实际上,由于诸如UI控件的标记字段之类的用途,在某些情况下需要允许这样的强制转换 - 它们不能完全取缔。

          enum的情况下,我们不能让编译器阻止从字符串到任何地方的转换,除非在类本身的实现中,就像StringConstants extra&#34; enum&#34;文字可以被嫁接。这条规则需要纪律。

          但是,如果缺乏规则,编译器可以帮助阻止除了强制转换之外的所有方法,这些方法可用于创建enum值,因此可以用NSString值作为子类。换句话说,所有StringConstantinitX等变体都可以在stringX上标记为无法使用。只需将其列在StringConstant并添加@interface

          即可完成此操作

          你没有需要来做这件事,上面的答案却没有,但是如果你需要这个帮助你的学科,你可以添加下面的声明 - 这个列表是由简单的产生的从NS_UNAVAILABLE复制并快速搜索&amp;取代

          NSString.h

答案 1 :(得分:-2)

您是否考虑过#define macro

kTabChart

在编译的预处理步骤中,编译器将换出所有const w /你想要的常量。

如果你想要自己的自定义类的常量,那么你必须使用{ "AC": 1.1324, "AD": 0.64956, "AE": 1.4508, "AF": -0.94481 } 作为@JeremyP用户在链接的答案中说的