Swift OC 混编Tips

记录一些 Swift 和 OC 混编时的一些 tips。

Objective-C 枚举宏

NS_ENUM

用于简单的枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Declare in Objective-C
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};

// In Swift, the UITableViewCellStyle enumeration is imported like this:
public enum UITableViewCellStyle : Int {
case `default` = 0
case value1 = 1
case value2 = 2
case subtitle = 3
}

NS_CLOSED_ENUM

用于不会变更枚举成员的简单的枚举(简称 “冻结枚举” )。对应 Swift 中的 @frozen 关键字。

相比较于非冻结枚举,冻结枚举降低了灵活性,但提升了性能。一旦枚举被标记为冻结枚举,那么在未来版本的库中就不能通过添加、删除或重新排序枚举的 case,否则会破坏 ABI 兼容性

1
2
3
4
5
6
7
8
9
10
11
12
13
// Declare in Objective-C
typedef NS_CLOSED_ENUM(NSInteger, NSComparisonResult) {
NSOrderedAscending = -1L,
NSOrderedSame,
NSOrderedDescending
};

// In Swift, the NSComparisonResult enumeration is imported like this:
@frozen public enum NSComparisonResult : Int {
case orderedAscending = -1
case orderedSame = 0
case orderedDescending = 1
}

使用 NS_ENUM 和 NS_CLOSED_ENUM 枚举宏在导入到 Swift 时生成的是实际 Enum 类型,而其它枚举宏都是生成 Struct 类型。

NS_OPTIONS

用于可选类型枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Declare in Objective-C
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

// In Swift, the UIViewAutoresizing type is imported like this:
public struct UIViewAutoresizing: OptionSet {
public init(rawValue: UInt)

public static var flexibleLeftMargin: UIViewAutoresizing { get }
public static var flexibleWidth: UIViewAutoresizing { get }
public static var flexibleRightMargin: UIViewAutoresizing { get }
public static var flexibleTopMargin: UIViewAutoresizing { get }
public static var flexibleHeight: UIViewAutoresizing { get }
public static var flexibleBottomMargin: UIViewAutoresizing { get }
}

NS_TYPED_ENUM

用于类型常量枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Store the three traffic light color options as 0, 1, and 2.
typedef long TrafficLightColor NS_TYPED_ENUM;

FOUNDATION_EXTERN TrafficLightColor const TrafficLightColorRed;
FOUNDATION_EXTERN TrafficLightColor const TrafficLightColorYellow;
FOUNDATION_EXTERN TrafficLightColor const TrafficLightColorGreen;

// In Swift, the TrafficLightColor type is imported like this:
public struct TrafficLightColor : Hashable, Equatable, RawRepresentable {
public init(rawValue: Int)
}
extension TrafficLightColor {
public static let red: TrafficLightColor
public static let yellow: TrafficLightColor
public static let green: TrafficLightColor
}

NS_STRING_ENUM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef NSString *DCDictionaryKey NS_STRING_ENUM;

FOUNDATION_EXTERN DCDictionaryKey const DCDictionaryKeyTitle;
FOUNDATION_EXTERN DCDictionaryKey const DCDictionaryKeySubtitle;
FOUNDATION_EXTERN DCDictionaryKey const DCDictionaryKeyCount;

// Objective-C 的常数被自动转换成 Swift Struct
public struct DCDictionaryKey : Hashable, Equatable, RawRepresentable {
public init(rawValue: String)
}
extension DCDictionaryKey {
public static let title : DCDictionaryKey
public static let subtitle : DCDictionaryKey
public static let count : DCDictionaryKey
}

NS_TYPED_EXTENSIBLE_ENUM

用于可扩展的类型常量枚举

1
2
3
4
5
6
7
8
9
10
11
12
// declared
typedef long FavoriteColor NS_TYPED_EXTENSIBLE_ENUM;
FOUNDATION_EXTERN FavoriteColor const FavoriteColorBlue;

// imported
public struct FavoriteColor : Hashable, Equatable, RawRepresentable {
public init(_ rawValue: Int)
public init(rawValue: Int)
}
extension FavoriteColor {
public static let blue: FavoriteColor
}

小结

  • 使用 NS_ENUM 和 NS_OPTIONS 来声明简单枚举和选项枚举,以优化 Swift 编程体验。

  • NS_CLOSED_ENUM 用于声明不会变更枚举成员的冻结枚举,对应 Swift 中的 @frozen 关键字,以降低灵活性的代价,换取了性能上的提升。

  • NS_STRING_ENUM/NS_EXTENSIBLE_STRING_ENUM、NS_TYPED_ENUM/NS_TYPED_EXTENSIBLE_ENUM 用于声明字符串常量/类型常量枚举,这在混编时在 Swift 中使用起来更简洁优雅更符合 Swift 的使用习惯。

Objective-C 类型定义 typedef 转换

NS_SWIFT_BRIDGED_TYPEDEF

未使用,导致类型冲突

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Objective-C Interface
typedef NSString * TimerID;

@interface Timer : NSObject
+ (void)cancelTimer:(TimerID)timerID NS_SWIFT_NAME(cancel(timerID:));

@end

// Generated Swift Interface
public typealias TimerID = NSString

open class Timer : NSObject {
open class func cancel(timerID: String)
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Objective-C Interface
typedef NSString * TimerID NS_SWIFT_BRIDGED_TYPEDEF;

@interface Timer : NSObject
+ (void)cancelTimer:(TimerID)timerID NS_SWIFT_NAME(cancel(timerID:));

@end

// Generated Swift Interface
public typealias TimerID = String // change: NSString -> String

open class Timer : NSObject {
open class func cancel(timerID: TimerID) // change: String -> TimerID
}

除了 NSString,NS_SWIFT_BRIDGED_TYPEDEF 还可以用在 NSDate、NSArray 等其它 Objective-C 类型别名中。

Objective-C API 重命名至 Swift

NS_SWIFT_NAME

  • 类、协议(宏用作前缀)
  • 枚举、属性、方法或函数、类型别名等其它所有类型(宏用作后缀)

使用前:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Objective-C Interface
typedef NS_ENUM(NSInteger, DisplayMode) {
DisplayMode256Color,
DisplayModeThousandsOfColors,
DisplayModeMillionsOfColors
};

// Generated Swift Interface
enum DisplayMode : Int {
case mode256Colors = 0
case modeThousandsOfColors = 1
case modeMillionsOfColors = 2
}

使用后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Objective-C Interface
typedef NS_ENUM(NSInteger, DisplayMode) {
DisplayMode256Colors NS_SWIFT_NAME(with256Colors),
DisplayModeThousandsOfColors,
DisplayModeMillionsOfColors,
};

// Generated Swift Interface
enum DisplayMode : Int {
case with256Colors = 0
case thousandsOfColors = 1
case millionsOfColors = 2
}

使用前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Objective-C Interface
@interface SandwichPreferences : NSObject
@property BOOL includesCrust;
@end

@interface Sandwich : NSObject
@end

// Generated Swift Interface
open class SandwichPreferences : NSObject {
open var includesCrust: Bool
}

open class Sandwich : NSObject {
}

使用后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Objective-C Interface
NS_SWIFT_NAME(Sandwich.Preferences) // 宏用作前缀
@interface SandwichPreferences : NSObject
@property BOOL includesCrust NS_SWIFT_NAME(isCrusty); // 宏用作后缀
@end

@interface Sandwich : NSObject
@end

// Generated Swift Interface
extension Sandwich {
open class Preferences : NSObject {
open var isCrusty: Bool
}
}

open class Sandwich : NSObject {
}

常用的使用方法

重命名 Objective-C 方法

1
2
3
4
5
// Declare in Objective-C
- (NSSet<NSString *> *)previousMissionsFlownByAstronaut:(SKAstronaut *)astronaut NS_SWIFT_NAME(previousMissions(flownBy:));

// Generated Swift Interface
open func previousMissions(flownBy astronaut: SKAstronaut) -> Set<String>

为 Swift 改进 Objective-C API

NS_REFINED_FOR_SWIFT

可用于在 Swift 中隐藏 Objective-C API,以便在 Swift 中提供相同 API 的更好版本,同时仍然可以使用原始 Objective-C 实现。

生成的 Swift API 将会以双下划线 __ 开头重命名,且在 Swift 中调用时不会有代码补全提示,相当于隐藏了 API。这样可以一定程度上防止调用者意外地直接使用该 Objective-C API,而没有使用适配后的 Swift API。

使用前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface Color : NSObject

/// 获取颜色的 rgba 值
- (void)getRed:(nullable CGFloat *)red
green:(nullable CGFloat *)green
blue:(nullable CGFloat *)blue
alpha:(nullable CGFloat *)alpha;

@end

/// 默认生成的 Swift
open func getRed(_ red: UnsafeMutablePointer<CGFloat>?,
green: UnsafeMutablePointer<CGFloat>?,
blue: UnsafeMutablePointer<CGFloat>?,
alpha: UnsafeMutablePointer<CGFloat>?)

使用后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@interface Color : NSObject

- (void)getRed:(nullable CGFloat *)red
green:(nullable CGFloat *)green
blue:(nullable CGFloat *)blue
alpha:(nullable CGFloat *)alpha NS_REFINED_FOR_SWIFT;

@end

/// 在 Swift 中增加适配的 API
extension Color {
var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
var r: CGFloat = 0.0
var g: CGFloat = 0.0
var b: CGFloat = 0.0
var a: CGFloat = 0.0
__getRed(&r, green: &g, blue: &b, alpha: &a) // 调用默认转换的实现
return (red: r, green: g, blue: b, alpha: a)
}
}

使用前:

1
2
3
4
5
6
7
8
// Declare in Objective-C
@interface MyClass : NSObject
/// 返回值为 NSNotFound 或者 Int
- (NSInteger)indexOfString:(NSString *)aString;
@end

// Generated Swift Interface
open func index(of aString: String) -> Int

使用后:

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface MyClass : NSObject
- (NSInteger)indexOfString:(NSString *)aString NS_REFINED_FOR_SWIFT;
@end

extension MyClass {
func index(of aString: String) -> Int? {
let index = Int(__index(of: aString))
if (index == NSNotFound) {
return nil
}
return index
}
}

API 限制

NS_SWIFT_UNAVAILABLE

标记 Objective-C API 在 Swift 中不可用。

Objective-C 适配 Optional

可空性限定符 导入到 Swift 中 意义
nullable、_Nullable 、__nullable optional,如 String? 该值可以是 nil
nonnull、_Nonnull、__nonnull non-optional,如 String 该值永远不会为 nil
null_unspecified、_Null_unspecified 、__null_unspecified 隐式解析 optional,如 String! 未指定时的默认效果
null_resettable(只用于属性) 隐式解析 optional,如 String! 可 set 为 nil,重置为默认值,但 getter 永远不会返回 nil。必须重写 setter 或 getter 做非空处理

使用规范

  • 弃用双下划线版本(如 __nullable)因为 Apple 保留它只是为了与 Xcode 6.3 兼容。
  • 对于属性以及方法中返回值和参数是简单对象或 Block 的指针类型,使用 nullable 版本;其它都使用 _Nullable 版本。

使用举例

  • 属性

    1
    2
    // Preferred
    @property (nullable, nonatomic, copy) NSString *name;
  • 方法中,返回值和参数是简单对象或 Block 的指针类型,使用 nullable 版本,写在类型前面

    1
    - (nullable MyListItem *)itemWithName:(nonnull NSString *)name block:(nullable void (^)(void))block;
  • 在 C 函数中,Block 的指针类型只能用 _Nullable 版本了

    1
    void block(void (^ _Nullable block)(void));
  • 对于双指针、Block 的返回值、Block 的参数等其它所有支持的指针类型,不能用 nullable 版本修饰,只能使用 _Nullable 版本,写在类型后面

Block 的返回值和参数类型,如果没有指定可空性的话,默认导入到 Swift 中是可选类型,而不是隐式解析可选类型。

1
2
- (void)param:(NSString * _Nullable * _Nullable)param;
- (void)param:(_Nullable id * _Nullable)param; // 也可以写成 (id _Nullable * _Nullable)

NS_ASSUME_NONNULL_BEGIN 和 NS_ASSUME_NONNULL_END

默认生成的 OC 类的声明文件内,会自动加上这两个宏。

在这两个宏之间的代码,所有未指定可空性限定符的简单指针类型都被假定为 nonnull,因此我们只需要去指定那些 nullable 指针类型即可。