Swift和OC混编下的Enumeration

在Swift和OC混编的代码中,不可避免的会涉及到Enumeration的混编。这篇文章除了介绍基础的混编知识,还想讲讲混编中遇到的坑。

如何import

Enum在Swift和Objective-C的实现机制不一样,Enum在Objective-C中仅是一个整数值,但在Swift却中非常强大,可以添加方法、添加extension,class能做的事情它几乎都可以。那如何在Swift和Objective-C中使用对方定义的Enum?

基本技巧

对于Swift而言,实现起来非常简单,定义Enum时提供一个Int型的rawValue,并添加@objc就可以了。如下所示:

@objc
enum SwiftEnum: Int {
    case invalid = 0
    case a = 1
    case b
    case c
}

对于Objective-C而言,只要是使用NS_ENUM宏定义的Enum都会自动转为Swift版的Enum。

typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
   UITableViewCellStyleDefault,
   UITableViewCellStyleValue1,
   UITableViewCellStyleValue2,
   UITableViewCellStyleSubtitle
};

在Swift中,它们会自动被import成下面的形式:

enum UITableViewCellStyle: Int {
    case `default`
    case value1
    case value2
    case subtitle
}

而没有使用NS_ENUMNS_OPTIONS宏定义的Enum,则会被import成为一个global read-only computed property。比如,下面的Enum,

typedef enum {
   MessageDispositionUnread = 0,
   MessageDispositionRead = 1,
   MessageDispositionDeleted = -1,
} MessageDisposition;

在Swift中会被导入为

struct MessageDisposition: RawRepresentable, Equatable {}
 
var MessageDispositionUnread: MessageDisposition { get }
var MessageDispositionRead: MessageDisposition { get }
var MessageDispositionDeleted: MessageDisposition { get }

注意事项

Objective-C Enum的无效rawValue

在Swift中使用Enum.init(rawValue:)时,若传入的rawValue没有对应的case,这个initialization方法会失败,返回nil。不过当我们在Swift中调用这个方法来初始化一个Objective-C的Enum时,即使传入rawValue是无效的,这个initialization方法也不会返回nil。这主要是为了兼容Objective-C,因为在Objective-C中,对于一个enum的变量,我们可以将它赋值为任意int值。

在Objective-C头文件中使用的Enum

目前没有找到合适的方法在Objective-C头文件中使用Swift的Enum,这种情况下,只能把那个Enum定义为Objective-C风格的。

尽量使用Swift版的Enum

当我们决定要定义一个Enum时,一定要优先将它写成Swift风格的。不仅是因为Swift版的更强大,更是因为它可以避免switch时的诡异bug!(具体情况见下一节)。

Enum的switch操作

以前从没觉得Enum的switch有什么好讲的,直到在Swift中switch OC语言的Enum时遇到了下面的这个大坑。。。

以下测试都是基于Xcode 9.3,Swift 4.1。

在Swift中switch OC语言的Enum

使用Swift switch C语言的enum,输入一个无效的rawValue时,系统不会报错,且调用Enum.init(rawValue:)不会返回nil,运行结果与switch列出的case和是否开启Build Settings >> Optimization Level相关:

  • switch中列出了该enum的所有case,那么第一个case会被执行!非常奇怪的现象。
  • switch中提供了default,且仅缺失一个case,
    • 若没开启Optimization(Debug模式的默认设置)时:default会被执行。 - 若开启了Optimization(Release模式的默认设置):第一个case会被执行!这是一个非常非常奇怪的现象。因为一方面,在我们已经提供了default的情况下,default都不会执行;另一方面,更诡异的是,在关闭Optimization后,default就会被执行了。按照Xcode的配置,Debug模式下是默认关闭Optimization,Release是默认开启Optimization。因此你可能在自己开发的过程中一切正常,但一上线就出现奇怪的bug,非常坑爹。
  • switch中提供了default,且缺失两个及以上的case,default会被执行。和我们的预期一样。

对于上面的奇怪现象,因为是系统行为,目前并没有找到合适的解决方案,在开发过程中可以注意以下两点:

  • 混编时如果需要定义Enum,尽量定义为Swift版本的。
  • 如果必须定义Objective-C版的时,可以在Enum中多写两个无意义的case,并在comment中注明不要使用。如下面的代码所示。这个方法有点过于简单粗暴,但实在是想不到更好的方法了。。。
// original Enum
typedef NS_ENUM(NSInteger, CEnum) {
    CEnumInvalid = 0,
    CEnumA = 1,
    CEnumB,
    CEnumC
};

// new Enum
typedef NS_ENUM(NSInteger, CEnum) {
     /// Don't use it. Compatibility with Swift only.
    CEnumInvalid1 = -1,
     /// Don't use it. Compatibility with Swift only.
    CEnumInvalid2 = -2,
 
    CEnumInvalid = 0,
    CEnumA = 1,
    CEnumB,
    CEnumC
};

测试代码如下:

// define Enum
typedef NS_ENUM(NSInteger, CEnum) {
    CEnumInvalid = 0,
    CEnumA = 1,
    CEnumB,
    CEnumC
};

// define test case
class EnumTest: NSObject {
    /// test objc enmu
    static func testObjcEnum(raw: Int) {
        print("----------- \(raw): Objc enum start in Swift ------------")
        defer {
            print("\n")
        }

        guard let aEnum = CEnum(rawValue: raw) else {
            print("invalid raw value")
            return
        }
        print("enmu.raw = \(aEnum.rawValue), enum = \(aEnum)")

        print(">>>>>> Full cases:")
        switch aEnum {
        case .A:
            print("match a")
        case .B:
            print("match b")
        case .C:
            print("match c")
        case .invalid:
            print("match invalid")
        }

        print(">>>>>> Miss one case:")
        switch aEnum {
        case .A:
            print("match a")
        case .B:
            print("match b")
        case .C:
            print("match c")
        default:
            print("match default")
        }

        print(">>>>>> Miss two cases:")
        switch aEnum {
        case .A:
            print("match a")
        case .B:
            print("match b")
        default:
            print("match default")
        }
    }
}

// run test case
    let intList: [Int] = [
        1,
        96,
        ]

    intList.forEach { (value) in
        EnumTest.testObjcEnum(raw: value)
    }

开启Optimization的log如下:

----------- 1: Objc enum start in Swift ------------
enmu.raw = 1, enum = CEnum
>>>>>> Full cases:
match a
>>>>>> Miss one case:
match a
>>>>>> Miss two cases:
match a

----------- 96: Objc enum start in Swift ------------
enmu.raw = 96, enum = CEnum
>>>>>> Full cases:
match a
>>>>>> Miss one case:
match a
>>>>>> Miss two cases:
match default


关闭Optimization的log如下:

----------- 1: Objc enum start in Swift ------------
enmu.raw = 1, enum = CEnum
>>>>>> Full cases:
match a
>>>>>> Miss one case:
match a
>>>>>> Miss two cases:
match a


----------- 96: Objc enum start in Swift ------------
enmu.raw = 96, enum = CEnum
>>>>>> Full cases:
match a
>>>>>> Miss one case:
match default
>>>>>> Miss two cases:
match default

在Swift中switch Swift的Enum

符合预期,使用无效的rawValue生成enum case时会返回nil,所以一切运行正常。

测试代码如下:

// define Enum
enum SwiftEnum: Int {
    case invalid = 0
    case a = 1
    case b
    case c
}

// define test case
class EnumTest: NSObject {
    /// test swift enmu
    static func testSwiftEnum(raw: Int) {
        print("----------- \(raw): swift enum start in Swift ------------")
        defer {
            print("\n")
        }

        guard let aEnum = SwiftEnum(rawValue: raw) else {
            print("invalid raw value")
            return
        }
        print("enmu.raw = \(aEnum.rawValue), enum = \(aEnum)")

        print(">>>>>> Full cases:")
        switch aEnum {
        case .a:
            print("match a")
        case .b:
            print("match b")
        case .c:
            print("match c")
        case .invalid:
            print("match invalid")
        }

        print(">>>>>> Miss one case:")
        switch aEnum {
        case .a:
            print("match a")
        case .b:
            print("match b")
        case .c:
            print("match c")
        default:
            print("match default")
        }

        print(">>>>>> Miss two cases:")
        switch aEnum {
        case .a:
            print("match a")
        case .b:
            print("match b")
        default:
            print("match default")
        }
    }
  
}

// run the test
    let intList: [Int] = [
        1,
        96,
        ]

    intList.forEach { (value) in
        EnumTest.testSwiftEnum(raw: value)
    }

无论Optimization的状态如何,都有如下的log:

----------- 1: swift enum start in Swift ------------
enmu.raw = 1, enum = SwiftEnum
>>>>>> Full cases:
match a
>>>>>> Miss one case:
match a
>>>>>> Miss two cases:
match a


----------- 96: swift enum start in Swift ------------
invalid raw value

在OC中switch OC语言的Enum

符合预期,使用无效的rawValue时,若switch中包含了全套的case,则一个case都不会执行。

测试代码如下:


// define enum
typedef NS_ENUM(NSInteger, CEnum) {
    CEnumInvalid = 0,
    CEnumA = 1,
    CEnumB,
    CEnumC
};

@implementation OCEnumTest

// define test case
+ (void)testSwiftEnumWithRaw:(NSInteger)raw {
    SwiftEnum aEnum = raw;
    NSLog(@"----------- \(%ld): Swift enum start in OC  ------------", (long)aEnum);

    NSLog(@">>>>>> Full cases:");
    switch (aEnum) {
        case SwiftEnumA:
            NSLog(@"match a");
            break;
        case SwiftEnumB:
            NSLog(@"match b");
            break;
        case SwiftEnumC:
            NSLog(@"match c");
            break;
        case SwiftEnumInvalid:
            NSLog(@"match invalid");
            break;
    }

    NSLog(@">>>>>> Miss one case:");
    switch (aEnum) {
        case SwiftEnumA:
            NSLog(@"match a");
            break;
        case SwiftEnumB:
            NSLog(@"match b");
            break;
        case SwiftEnumC:
            NSLog(@"match c");
            break;
        default:
            NSLog(@"match default");
            break;
    }

    NSLog(@">>>>>> Miss two cases:");
    switch (aEnum) {
        case SwiftEnumA:
            NSLog(@"match a");
            break;
        case SwiftEnumB:
            NSLog(@"match b");
            break;
        default:
            NSLog(@"match default");
            break;
    }
}

// run test case
    let intList: [Int] = [
        1,
        96,
        ]

    intList.forEach { (value) in
        OCEnumTest.testOCEnum(raw: value)
    }

@end

Log如下:

2018-05-25 17:09:56.207375 TestSwift2[7396:2894846] ----------- (1): OC enum start in OC ------------
2018-05-25 17:09:56.207520 TestSwift2[7396:2894846] >>>>>> Full cases:
2018-05-25 17:09:56.207560 TestSwift2[7396:2894846] match a
2018-05-25 17:09:56.207596 TestSwift2[7396:2894846] >>>>>> Miss one case:
2018-05-25 17:09:56.207630 TestSwift2[7396:2894846] match a
2018-05-25 17:09:56.207666 TestSwift2[7396:2894846] >>>>>> Miss two cases:
2018-05-25 17:09:56.207700 TestSwift2[7396:2894846] match a
2018-05-25 17:09:56.207801 TestSwift2[7396:2894846] ----------- (96): OC enum start in OC ------------
2018-05-25 17:09:56.207836 TestSwift2[7396:2894846] >>>>>> Full cases:
2018-05-25 17:09:56.207870 TestSwift2[7396:2894846] >>>>>> Miss one case:
2018-05-25 17:09:56.207904 TestSwift2[7396:2894846] match default
2018-05-25 17:09:56.207937 TestSwift2[7396:2894846] >>>>>> Miss two cases:
2018-05-25 17:09:56.207971 TestSwift2[7396:2894846] match default

在OC中switch Swift的Enum

符合预期,使用无效的rawValue时,若switch中包含了全套的case,则一个case都不会执行。

测试代码如下:


// define enum
typedef NS_ENUM(NSInteger, CEnum) {
    CEnumInvalid = 0,
    CEnumA = 1,
    CEnumB,
    CEnumC
};

@implementation OCEnumTest

// define test case
+ (void)testOCEnumWithEnum:(CEnum)aEnum {
    NSLog(@"----------- \(%ld): OC enum start in OC ------------", (long)aEnum);

    NSLog(@">>>>>> Full cases:");
    switch (aEnum) {
        case CEnumA:
            NSLog(@"match a");
            break;
        case CEnumB:
            NSLog(@"match b");
            break;
        case CEnumC:
            NSLog(@"match c");
            break;
        case CEnumInvalid:
            NSLog(@"match invalid");
            break;
    }

    NSLog(@">>>>>> Miss one case:");
    switch (aEnum) {
        case CEnumA:
            NSLog(@"match a");
            break;
        case CEnumB:
            NSLog(@"match b");
            break;
        case CEnumC:
            NSLog(@"match c");
            break;
        default:
            NSLog(@"match default");
            break;
    }

    NSLog(@">>>>>> Miss two cases:");
    switch (aEnum) {
        case CEnumA:
            NSLog(@"match a");
            break;
        case CEnumB:
            NSLog(@"match b");
            break;
        default:
            NSLog(@"match default");
            break;
    }
}

// run test case
    let intList: [Int] = [
        1,
        96,
        ]

    intList.forEach { (value) in
        OCEnumTest.testOCEnum(raw: value)
    }

@end

Log如下:

2018-05-25 17:20:26.036986 TestSwift2[7406:2897353] ----------- (1): Swift enum start in OC  ------------
2018-05-25 17:20:26.037106 TestSwift2[7406:2897353] >>>>>> Full cases:
2018-05-25 17:20:26.037191 TestSwift2[7406:2897353] match a
2018-05-25 17:20:26.037228 TestSwift2[7406:2897353] >>>>>> Miss one case:
2018-05-25 17:20:26.037264 TestSwift2[7406:2897353] match a
2018-05-25 17:20:26.037299 TestSwift2[7406:2897353] >>>>>> Miss two cases:
2018-05-25 17:20:26.037333 TestSwift2[7406:2897353] match a
2018-05-25 17:20:26.037371 TestSwift2[7406:2897353] ----------- (96): Swift enum start in OC  ------------
2018-05-25 17:20:26.037406 TestSwift2[7406:2897353] >>>>>> Full cases:
2018-05-25 17:20:26.037443 TestSwift2[7406:2897353] >>>>>> Miss one case:
2018-05-25 17:20:26.037478 TestSwift2[7406:2897353] match default
2018-05-25 17:20:26.037513 TestSwift2[7406:2897353] >>>>>> Miss two cases:
2018-05-25 17:20:26.037548 TestSwift2[7406:2897353] match default
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342