iOS混编参考阅读

鉴于 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_BEGINNS_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_SWIFTNS_SWIFT_NAME 一起用的话,NS_REFINED_FOR_SWIFT 不生效,而是以 NS_SWIFT_NAME 指定的名称重命名 Objective-C API。

猜你喜欢

转载自blog.csdn.net/qq_14920635/article/details/121495403