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

你可能感兴趣的:(Swift和OC混编下的Enumeration)