鉴于 Objective 已经打下了一大片江山,但是还是要将江山与 Swift 共享,所以就涉及到项目使用 Objective-C 和 Swift混编,如果让这两种语言更加相互融合,充分利用 Swift 的语言优势了。
怎样让 Objective-C 更便利桥接给 Swift
以下内容来源于师大小海腾–掘金,详情建议阅读原文,原文有许多举例和相关参考阅读。
iOS 混编|为 Objective-C API 指定可空性
关键词:nullable、nonull、null_resettable、null_resettable、NS_ASSUME_NONNULL_BEGIN/NS_ASSUME_NONNULL_END
通过 nullability annotations 为 Objective-C API指定可空性,更好的接入 Swift 和提高表现力。
由于兼容性,会有如下版本:
可空性限定符 | 导入到 Swift 中 | 意义 |
---|---|---|
nullable、_Nullable 、__nullable | optional,如 String? | 该值可以是 nil |
nonnull、_Nonnull、__nonnull | non-optional,如 String | 该值永远不会为 nil |
null_unspecified、_Null_unspecified 、__null_unspecified | 隐式解析 optional,如 String! | 未指定值是否可以 nil(非常罕见,除非将其作为过渡工具,否则应避免使用)。使用该限定符的结果和不使用 nullability annotations 特性的结果是一样的,我认为该限定符的作用是在过渡时抵消 audited Regions |
null_resettable(只用于属性) | 隐式解析 optional,如 String! | 1. 属性的 setter 允许设置 nil 以将值重置为某个默认值,但其 getter 永远不会返回 nil(因为提供了一个默认值); 2. 必须重写 setter 或 getter 做非空处理。否则会报警告 Synthesized setter 'setName:' for null_resettable property 'name' does not handle nil ,同时存在安全隐患 |
注意:nullability annotations 不能用于非指针类型,因为 Objective-C 中 nil 只能用在引用对象的指针上,而对于基础数据类型如 NSInteger 对于没有值的情况我们一般是使用 NSNotFound。在 iOS 混编|为 Swift 改进 Objective-C API 声明 有讲解如何在 Swift 中将 Objective-C 的 NSNotFound 改进为 nil 的例子,可以看看。
建议使用 nullable、nonnull 和 null_unspecified
Audited Regions:Nonnull区间
在 Objective-C API 中,许多指针往往是 nonnull 的。而且如果每个属性或方法都去指定 nonnull 或 nullable,将是一件非常繁琐的事。Apple 为了减轻我们的工作量,提供了 audited Regions,也就是两个宏:NS_ASSUME_NONNULL_BEGIN
和 NS_ASSUME_NONNULL_END
。在这两个宏之间的代码,所有未指定可空性限定符的简单指针类型都被假定为 nonnull,因此我们只需要去指定那些 nullable 指针类型即可。
NS_ASSUME_NONNULL_BEGIN
@interface AAPLList : NSObject <NSCoding, NSCopying>
// ...
- (nullable AAPLListItem *)itemWithName:(NSString *)name;
- (NSInteger)indexOfItem:(AAPLListItem *)item;
@property (copy, nullable) NSString *name;
@property (copy, readonly) NSArray *allItems;
// ...
@end
NS_ASSUME_NONNULL_END
对闭包无效
iOS 混编|为 Objective-C 添加枚举宏,改善混编体验
关键词:NS_ENUM、NS_OPTIONS、NS_CLOSED_ENUM、NS_TYPED_ENUM、NS_TYPED_EXTENSIBLE_ENUM、NS_STRING_ENUM、NS_EXTENSIBLE_STRING_ENUM、@unknown default
充分介绍 Objective-C中各种枚举的介绍和使用场景,可以加强对 Objective-C 中枚举的认识,用好他们将大大提高混编中 Swift 的编程体验。
枚举类型 | 简介 |
---|---|
NS_ENUM | 用于简单的枚举 |
NS_OPTIONS | 用于可选类型枚举(Swift中转换为遵循OptionSet 结构体 ) |
NS_CLOSED_ENUM | 用于不会变更枚举成员的简单的枚举(简称 “冻结枚举” ) |
NS_TYPED_ENUM | 用于类型常量枚举 |
NS_TYPED_EXTENSIBLE_ENUM | 用于可扩展的类型常量枚举 |
NS_STRING_ENUM | 用于类型常量枚举,不建议使用 |
NS_EXTENSIBLE_STRING_ENUM | 用于可扩展的类型常量枚举,不建议使用 |
使用 NS_ENUM 和 NS_CLOSED_ENUM 枚举宏在导入到 Swift 时生成的是实际 Enum 类型,而其它枚举宏都是生成 Struct 类型。
iOS 混编|限制 API 可用性
关键词:NS_SWIFT_UNAVAILABLE、NS_UNAVAILABLE、@available
全版本
- 使用
NS_SWIFT_UNAVAILABLE
宏使 Objective-C API 在 Swift 中不可用 - 使用
NS_UNAVAILABLE
宏使 Objective-C API 在 Objective-C 和 Swift 中都不可用
判断版本或者平台
// Objective-C
@interface MyViewController : UIViewController
- (void)newMethod API_AVAILABLE(ios(11), macosx(10.13));
@end
// Swift
@available(iOS 11, macOS 10.13, *)
func newMethod() {
// Use iOS 11 APIs.
}
使用
// Objective-C
if (@available(iOS 11, *)) {
// Use iOS 11 APIs.
} else {
// Alternative code for earlier versions of iOS.
}
// Swift
if #available(iOS 11, *) {
// Use iOS 11 APIs.
} else {
// Alternative code for earlier versions of iOS.
}
iOS 混编|为 Swift 重命名 Objective-C API
关键词: NS_SWIFT_NAME、@objc
解决了 Objective-C 与 Swift 命名不规范造成不痛快体验,通过NS_SWIFT_NAME
宏来为 Swift 重命名,涉及多种示例情况,比如将Objective-C某种类型转为Swift 命名空间的内部类型。
最后我们再来回顾下,Apple 给的示例中 NS_SWIFT_NAME
的应用场景:
- 重命名与 Swift 风格不符的 API,使其在 Swift 中有合适的名称;
- 将与类 A 相关联的类/枚举作为内部类/枚举附属于类 A;
- 重命名 “命名去掉完整前缀后以数字开头的” 枚举的 case,改善所有 case 导入到 Swift 中的命名;
- 重命名 “命名不满足自动转换为构造器导入到 Swift 中的约定的” 工厂方法,使其作为构造器导入到 Swift 中(不能用于协议中);
- 在处理全局常量、变量,特别是在处理全局函数时,它的能力更加强大,能够极大程度地改变 API。比如可以将
全局函数
转变为静态方法
,或是实例⽅法
,甚至是实例属性
。如果你在 Objective-C 和 Swift 里都用过 Core Graphics 的话,你会深有体会。Apple 称其把NS_SWIFT_NAME
用在了数百个全局函数上,将它们转换为方法、属性和构造器,以更加方便地在 Swift 中使用。
iOS 混编|将 Objective-C typedef NSString 作为 String 桥接到 Swift 中
关键词:NS_SWIFT_BRIDGED_TYPEDEF
解决 Objective-C 使用 typedef 定义,桥接后 Swift使用冲突问题。了解如何用NS_SWIFT_BRIDGED_TYPEDEF
宏优雅解决该问题。
iOS 混编|为 Swift 改进 Objective-C API
关键词:NS_REFINED_FOR_SWIFT
该宏通过影藏方法名、属性,之后在 Swift 端再次写代码调用封装,来提供更好的 API 版本。
具体的应用场景有:
- 你想在 Swift 中使用某个 Objective-C API 时,使用不同的方法声明,但要使用类似的底层实现
- 你想在 Swift 中使用某个 Objective-C API 时,采用一些 Swift 的特有类型,比如元组(具体例子可以看 Example_Apple)
- 你想在 Swift 中使用某个 Objective-C API 时,重新排列、组合、重命名参数等等,以使该 API 与其它 Swift API 更匹配
- 利用 Swift 支持默认参数值的优势,来减少导入到 Swift 中的一组 Objective-C API 数量(具体例子可以看 Example_SDWebImage)
- 解决 Swift 调用 Objective-C 的 API 时可能由于数据类型等不一致导致无法达到预期的问题。例如,Objective-C 里的方法采用了 C 风格的多参数类型;或者 Objective-C 方法返回 NSNotFound,在 Swift 中期望返回 nil 等等(具体例子可以看 Example_Other)
影藏规则
NS_REFINED_FOR_SWIFT
可用于初始化方法、属性、其它方法。添加了 NS_REFINED_FOR_SWIFT
的 Objective-C API 在导入到 Swift 时,具体的 API 重命名规则如下:
- 如果是初始化方法,则在其第一个参数标签前面加 “__”
// Objective-C API
- (instancetype)initWithColor:(UIColor *)color NS_REFINED_FOR_SWIFT;
// Use it in Swift
let color = Color(__color: .red)
复制代码
- 其它方法,在方法名前面加 “__”
// Objective-C API
+ (void)method NS_REFINED_FOR_SWIFT;
// Use it in Swift
Color.__method()
复制代码
- 下标方法将被视为任何其它方法,在方法名前面加 “__”,而不是作为 Swift 下标导入
- 其他声明将在其名称前加上 “__”,例如属性:
// Objective-C API
@property (nonatomic, copy) NSString *name NS_REFINED_FOR_SWIFT;
// Use it in Swift
object.__name = "zhangsan"
复制代码
注意:
NS_REFINED_FOR_SWIFT
和NS_SWIFT_NAME
一起用的话,NS_REFINED_FOR_SWIFT
不生效,而是以NS_SWIFT_NAME
指定的名称重命名 Objective-C API。