没有默认情况的Objective-C枚举“全面”的开关行为

3
在Swift中,init(rawValue:)系统确保将Int转换为枚举类型时,结果要么是有效的枚举case,要么是nil
在Objective-C中没有这种安全性,通过将非成员"rawValue"强制转换为枚举类型可能会创建无效的枚举成员。
typedef NS_ENUM(NSInteger, ExampleEnum) {
    first = 0,
    second,
    third,
};

+ (NSString *)stringForCase:(ExampleEnum)enumCase {
    switch (enumCase) {
        case first:  return @"first";
        case second: return @"second";
        case third:  return @"third";
    }
}

+ (void)testEnum {
    ExampleEnum invalidCase = (ExampleEnum)3; // this "rawValue" is out of bounds
    NSString *string = [self stringForCase:invalidCase]; // nil
}

当枚举类型被开启时,编译器会警告您未处理的枚举值:

在 switch 中未处理枚举值 'third'

但是一旦所有情况都已处理,就没有类似的警告表明仍然可能有一个无效的成员变量的“默认”情况。

在这种情况下会发生什么行为?NSString 方法似乎返回 nil,并且没有观察到崩溃。但是该方法没有 return。是否会自动生成 return nil,在什么情况下会生成?

请注意,“穷尽”switch 后的代码语句不会导致通常会生成的警告:

代码将永远不会被执行


1
有点题外话,但您可能会对这篇博客文章感兴趣(阅读速度很快):https://owensd.io/2014/10/09/building-swift-style-enums-in-objc-part-3/ - jscs
1
但是如果有人想要恶意攻击,把一个未初始化的整数扔进我的美妙函数里怎么办呢? - pkamb
顺便说一句:就C语言(或ObjC)中的枚举而言,与其他许多语言中的枚举不同,它们在概念上并不是“特殊”的类型——将枚举视为仅具有某些有用的编译器警告的整数的语法糖,但存在其他缺陷。对于未定义的枚举集中没有的值使用一些随机整数不太“无效”,而只是编写(或设计)不良的代码。由于这种设置,枚举在C中有点麻烦,几乎所有后来的语言(包括Swift)都使枚举更加一流和有用。 - Ben Zotto
1个回答

4
如果没有任何情况匹配,该函数会返回控制权给调用者,但返回值是未定义的。C/ObjC的return语句基本上做两件事情。它将其值放入特定位置,以便调用者知道在哪里查找它(位置由平台/语言ABI定义)。然后它通过从堆栈中弹出地址并跳转到该地址来将控制权移回调用函数。在这种情况下,控制权将直接跳过switch结构的末尾,并且不会执行任何return语句。然而,编译器确实为方法的结束生成了一个跳转。实际上,我不太擅长阅读汇编语言,但是我看到Xcode 10.1为您的代码创建的调试注释汇编代码将所有switch情况汇聚到一个方法的单个退出点。如果没有任何情况比较成功,则也会到达该退出点。但是,在没有将任何值放入返回寄存器的情况下,我们到达了那个跳转,这意味着它与任何其他未初始化的值相同-垃圾。您可靠地获得nil的事实可能是由于测试程序的简单性和使用调试配置构建的结果。

很高兴知道我得到的nil结果是未定义的。您是否知道这个确切的主题是否已经在其他地方被涵盖或写过? - pkamb
说实话,我不确定,但这是纯C的行为;在这种情况下,没有任何特定于ObjC的东西,所以它可能在C标准中。 - jscs
1
现在我想了想,ARC 可能会保证返回类型是对象时返回 nil,因为它也会这样做未初始化的对象指针。不太确定,我需要花些时间查看 ARC 规范。 - jscs
我在 ARC 文档中找到了一个线索,可能是这个问题导致的,但我不确定。我会再次查看汇编输出,看看能否找出任何线索。 - jscs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接