在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_ENUM
或NS_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,非常坑爹。
- 若没开启Optimization(Debug模式的默认设置)时:
- 若
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