2015-06-15 43 views
1

我在看一个代码库,里面满是有没有一种方法在Objective-C中具有类型安全的常量?

NSString *const kTabChart = @"Charts"; 
NSString *const kTabNews = @"News"; 

然后

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

所以这些弱类型的NSString走多远和周围的所有代码的方式,这只是刺激我的眼睛。枚举在某种程度上会更好,但是枚举不会以编程方式提供名称,并且我不想在同一个枚举中定义来自不同视图的所有不相关的选项卡名称。{0122}我不知道是否有更好的方法?我梦想的一种方式,使其像

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

我怀疑这会不会与整个“不是编译时间常数”约束打得很好,但我不知道是否有技巧/ patters /黑客定义我自己的类类型的常量。

+0

在你的PageTitle中添加类方法,返回你的常量。顺便说一句。你不能继承NSString。 –

+0

可以子类'NSString',但通常是一个糟糕的选择,拉完成并不容易。 – zaph

+1

这确实是枚举的用途。无论如何,这些UI的字符串并不属于那里。 – Eiko

回答

-2

您是否想过#define macro

#define kTabChart @"Charts" 

在编译过程中的预处理步骤,编译器将换出所有kTabChart瓦特/你所需的恒定。

如果你想自己的自定义类的常量,那么你将不得不使用const为用户@JeremyP在链接的答案

+2

宏不是类型安全的,应该使用rarley并且通常是非常糟糕的选择。 – zaph

+0

嗯,我不知道 - 我只是觉得它是类型安全的,因为编译器不会让你分配给宏。你能举个例子说明你可以修改宏的底层类型吗?我很难在脑海中围绕它 –

+0

#define没有类型,它基本上只是一个文本替换。在这种情况下,编译器可以进行类型确定,但一般不会。在这个例子中'#define x 3' x和3可以用作char,short,long,float等。'NSString * const kTabChart = @“Charts”;'kTabChart直接有一个类型。 – zaph

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. 加入额外的“枚举”值

第二是通过在测试代码第二缺少注释覆盖:与处理

[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中/锵:2的32次方, 2.以下是完全有效的:

PieOptions po = (PieOptions)42; 

现在,当你不应该写这样明显的错误代码需要整数和enum值之间的转换是很常见 - 例如将“枚举”值存储在UI控件的标记字段中时 - 以及错误的空间。 C风格列举必须与学科一起使用,并且使用这种方式对于编程正确性和可读性是很好的帮助。

以同样的方式StringConstants必须遵守纪律,没有铸造任意字符串 - 相当于上面的42个例子 - 并且与纪律他们有标准枚举类似的优点和缺点。

随着简单的纪律不铸造任何字符串StringConstants;这是允许的东西只有StringConstants执行本身;如果使用不正确,此类型将为您提供完全安全的“字符串值枚举”,并带有编译时警告。

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


附录:深入研究(只是好奇,或者我们不信任你)

要理解为什么StringConstants是完全安全的(甚至增加额外的价值是不是真的不安全的,虽然它可能的当然会导致程序逻辑失败),我们将通过一些关于面向对象编程,动态类型和Objective-C的问题。下面的声明并不是严格意义上的理解为什么StringConstants是安全的,但是你是一个有探究心态的人不是吗?

对象引用强制转换不运行时

一个从一个对象引用类型到另做任何事情是一个编译时声明说,应参考作为到的对象处理目的地类型。它在运行时对实际引用的对象没有影响 - 该对象具有实际类型,并且不会更改。在面向对象的模型上溯造型,从一个类将其超类的一个,总是安全,向下转换,在反向方向上行进,不得不是)是安全的。出于这个原因,向下广播应该在可能不安全的情况下通过测试来保护。例如,给定:

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,并且您获得编译时错误。

但是,如果downcast可能无效,只需要这样做,如果它不能无效,则不需要进行测试。

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

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

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

鸭子,NSProxy,继承等。人。

鸭子?!

在动态类型有一种说法:

如果它看起来像鸭子,游泳像鸭子,叫起来,并像鸭子,那么它就是鸭子。

在Objective-C而言,这意味着在运行时,如果被引用的对象所支持的集合的类型A的方法则是有效类型A不管它实际类型是什么的对象。

该功能在Objective-C中很多地方使用,一个显着的例子是NSProxy

NSProxy是一个抽象的超类定义的API对象充当替身的其他对象或对象那还不存在。通常,向代理发送的消息被转发给真实对象,或者导致代理加载(或转换成)真实对象。 NSProxy的子类可用于实现透明的分布式消息传递(例如,NSDistantObject)或用于创建昂贵对象的懒惰实例化。

随着NSProxy你可能认为你有,比如说,一个NSDictionary - 一些东西,“看起来,游泳和江湖医生”像字典 - 但事实上,你却没有让一个在所有。重要的一点是:

  1. 没关系;和
  2. 这是完全安全的(模编码错误,如果课程)

您可以查看该能力将一个对象替换另一个继承的推广 - 与后来你总是可以代替使用一个子类的实例一个超类,只要“看起来,游泳和嘎嘎”像它所站立的对象一样,前者可以用任何物体代替另一个物体。

实际上我们进一步超出我们的需求了,鸭子是不是真的需要了解StringConstants,所以让我们继续:

当是一个字符串的NSString一个实例?班所有同一套方法,这些方法NSString不响应的集合,即他们都嘎嘎像NSString -

大概从未 ...

NSString类簇实现。现在这些类可能NSString的子类,但在Objective-C中并不需要它们。

此外,如果你认为你有一个NSString的实例,你可能实际上有一个NSProxy的实例... 但是没关系。(那么它可能会影响性能,但不会影响安全性或正确性。)

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

只要StringConstants嘎嘎像NSString小号实例应该那么他们NSString秒 - 我们已经在实现中定义的所有实例做,因为他们字符串(某种类型的,可能__NSCFConstantString) 。

哪个让我们回答这个问题是定义了StringConstants常量的声音?这与以下相同的问题:

什么时候下降已知总是安全?

首先,当它不是一个例子:

如果你有一个引用类型为NSDictionary *那么不知道是安全它转换为NSMutableDictionary *没有第一测试引用是否一个可变的字典。

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

注意标准测试,isKindOf:,实际上是保守由于所有这些鸭子。你实际上可能会引用一个像NSMutableDictionary这样的对象,但它不是它的一个实例 - 所以这个对象是安全的,但测试会失败。

是什么让这个演员阵容不安全?

简单,它是不知道的参考对象是否响应,一个NSMutableDictionary确实的方法...

人机工程学,如果你也知道,参考必须来响应的所有方法你正在施放的类型是总是安全并且不需要测试。

那你怎么知道这个引用必须对目标类型的所有方法做出响应?

那么一个情况很简单:如果你有一个参考类型为T,您可以在到S类型安全的,没有任何检查whatsever如果一个参考:

  1. ST一个子类 - 所以它叫起来像T;
  2. S将实例状态(变量)添加到T;
  3. ST中没有添加实例行为(新方法,覆盖等);
  4. S覆盖无类的行为

S可能添加类新类的方法(未覆盖),并在不违反这些要求的全局变量/常量。

换句话说S被定义为:

@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 

而我们做到了...

还是说我们,任何人都还在读书?

  1. StringConstants通过设计构造成使得字符串实例可以投给它。这应该只有在执行中完成,偷偷在其他“枚举”常数其他地方违背了这个类的目的。
  2. 这是安全,实际上它甚至不是吓人
  3. StringConstants没有实际的情况是迄今为止:-),每个常量是一些字符串类的一个实例,安全地在编译时伪装成StringConstants实例。
  4. 它提供了编译时间检查字符串常量是否来自预定的一组值,它实际上是一个“字符串值枚举”。

然而,另一个附录:执行纪律

不能完全自动执行在Objective-C的安全规则要求的纪律。

尤其是,您不能让编译器阻止程序员将任意整数值转换为enum类型。事实上,由于使用诸如UI控件的标签字段,在某些情况下需要允许这样的演员 - 他们不能被完全取缔。

在我们不能有编译器的StringConstants的情况下防止从一个字符串投无处不除了在类本身的实现,就像enum额外的“枚举”文字可以被接上。这条规则需要纪律。

但是,如果纪律缺乏编译器可以帮助防止所有的方式,比其他铸造,可用于创建NSString值,因此StringConstant值,因为它是一个子类。换句话说,所有的initX,stringX等变化可以标记为StringConstant不可用。这是通过简单地列出他们在@interface并添加NS_UNAVAILABLE

做你不需要要做到这一点,答案上面没有,但如果你需要你的纪律这一援助,您可以添加下面的声明 - 这个清单是通过简单地从NSString.h复制而成,并且快速搜索&来代替。

+ (instancetype) new NS_UNAVAILABLE; 
+ (instancetype) alloc NS_UNAVAILABLE; 
+ (instancetype) allocWithZone:(NSZone *)zone NS_UNAVAILABLE; 

- (instancetype) init NS_UNAVAILABLE; 

- (instancetype) copy NS_UNAVAILABLE; 
- (instancetype) copyWithZone:(NSZone *)zone NS_UNAVAILABLE; 

- (instancetype)initWithCharactersNoCopy:(unichar *)characters length:(NSUInteger)length freeWhenDone:(BOOL)freeBuffer NS_UNAVAILABLE; 
- (instancetype)initWithCharacters:(const unichar *)characters length:(NSUInteger)length NS_UNAVAILABLE; 
- (instancetype)initWithUTF8String:(const char *)nullTerminatedCString NS_UNAVAILABLE; 
- (instancetype)initWithString:(NSString *)aString NS_UNAVAILABLE; 
- (instancetype)initWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2) NS_UNAVAILABLE; 
- (instancetype)initWithFormat:(NSString *)format arguments:(va_list)argList NS_FORMAT_FUNCTION(1,0) NS_UNAVAILABLE; 
- (instancetype)initWithFormat:(NSString *)format locale:(id)locale, ... NS_FORMAT_FUNCTION(1,3) NS_UNAVAILABLE; 
- (instancetype)initWithFormat:(NSString *)format locale:(id)locale arguments:(va_list)argList NS_FORMAT_FUNCTION(1,0) NS_UNAVAILABLE; 
- (instancetype)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding NS_UNAVAILABLE; 
- (instancetype)initWithBytes:(const void *)bytes length:(NSUInteger)len encoding:(NSStringEncoding)encoding NS_UNAVAILABLE; 
- (instancetype)initWithBytesNoCopy:(void *)bytes length:(NSUInteger)len encoding:(NSStringEncoding)encoding freeWhenDone:(BOOL)freeBuffer NS_UNAVAILABLE; 

+ (instancetype)string NS_UNAVAILABLE; 
+ (instancetype)stringWithString:(NSString *)string NS_UNAVAILABLE; 
+ (instancetype)stringWithCharacters:(const unichar *)characters length:(NSUInteger)length NS_UNAVAILABLE; 
+ (instancetype)stringWithUTF8String:(const char *)nullTerminatedCString NS_UNAVAILABLE; 
+ (instancetype)stringWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2) NS_UNAVAILABLE; 
+ (instancetype)localizedStringWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2) NS_UNAVAILABLE; 

- (instancetype)initWithCString:(const char *)nullTerminatedCString encoding:(NSStringEncoding)encoding NS_UNAVAILABLE; 
+ (instancetype)stringWithCString:(const char *)cString encoding:(NSStringEncoding)enc NS_UNAVAILABLE; 

- (instancetype)initWithContentsOfURL:(NSURL *)url encoding:(NSStringEncoding)enc error:(NSError **)error NS_UNAVAILABLE; 
- (instancetype)initWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error NS_UNAVAILABLE; 
+ (instancetype)stringWithContentsOfURL:(NSURL *)url encoding:(NSStringEncoding)enc error:(NSError **)error NS_UNAVAILABLE; 
+ (instancetype)stringWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error NS_UNAVAILABLE; 

- (instancetype)initWithContentsOfURL:(NSURL *)url usedEncoding:(NSStringEncoding *)enc error:(NSError **)error NS_UNAVAILABLE; 
- (instancetype)initWithContentsOfFile:(NSString *)path usedEncoding:(NSStringEncoding *)enc error:(NSError **)error NS_UNAVAILABLE; 
+ (instancetype)stringWithContentsOfURL:(NSURL *)url usedEncoding:(NSStringEncoding *)enc error:(NSError **)error NS_UNAVAILABLE; 
+ (instancetype)stringWithContentsOfFile:(NSString *)path usedEncoding:(NSStringEncoding *)enc error:(NSError **)error NS_UNAVAILABLE; 
+2

嗯...子类化NSString是一件很难的事情,即使是这个“简单”的任务。将NSString强制转换为你的子类并不会使它成为一个,所以它的指针现在正在散布对它的内容的谎言,这为未来打开了一堆好的蠕虫。另外,NSString是一个类集群,使其更难以继承。我认为这种方法虽然是一种“快速修复”,但从长远来看却让情况变得更糟,是对语言的严重滥用,应该被认为是一个巨大的红旗大NO-NO。 – Eiko

+0

@Eiko - 在这种情况下,您不必担心。从来没有任何'StringConstant'的实例,它们都只是'NSString'(或者使用类集群中的任何实际的类)。问一个它的类型,它不会说'StringConstant' - 它的指针不会比'NSString *'做更多的“谎言”。整个Objective-C都是这样工作的(尤其是认为'id'和class集群)。这给你的是编译时警告,根本不改变运行时动态类型。 – CRD

+0

@Eiko - 顺便说一句如果你真的想把它锁定进一步添加'-init','+ new','alloc'' -copy'和所有其他的{init,copy,alloc,string} *方法标记为'NS_UNAVAILABLE',因此无法创建实例。 (只需将它们从'NSString'中拷贝出来,添加'NS_UNAVAILABLE',并放在头文件中 - 不需要任何实现 – CRD

相关问题