猫·仁波切

会研发的PM才是好OP.

Swift and ObjectiveC Interop (Swift 与 Objective-C 之间的交互)

主要介绍原有 Objective-C 中的一些类型在 Swift 中的操作问题。差不多就能知道,你在 Swift 如何对 Objective-C (Foundation) 原生类型进行操作以及和 Swift 类型互转。

瞎写的,有错或者不明白的地方直接告诉我。转载注明我或者 SwiftChina 。

参考资料是 我的 Github : andelf/Defines-Swift 是我dump出的定义。10w行swift代码。

预备知识

Objective-C (Cocoa) 特色

数值类型统一用 NSNumber,不区分具体。大量操作在指针上进行,也就隐含着一切可能出错的调用都会返回 nil。 没有泛型支持,字典或者数组更多用 id 类型。

对应 Swift 特色

数值类型众多 (Int, UInt, Float, Double, ...),无指针(UnsafePointer 这类用于和其他代码交互的结构不计,& + inout 认为是引用。)。 nil 用于 Type?, Type!。字典和数组用泛型表示。

一些属性

有几个属性用于和 Objective-C 之间的交互。

@objc@objc(a_name_defined_in_objc)

@objc_block

用于函数类型前,标记后面的 block 是 Objective-C 的。个人觉得编译器会自动转换相关的 block 类型。

然后需要预备下几个属性的知识。

@conversion

Swift 中的隐式类型转换。有另一篇文章介绍。

@final

就是 final 了。

@transparent

经过反汇编查看,相当于 inline 的作用。同时生成的 .swiftmodule 文件依然带有原函数实现。这个很高大上。保证了 inline 特性在包外可用。

重要 Swift protocol

*LiteralConvertible

表示可以从 Swift 字面常量转换而得。比如 let a: Type = 100, 那么 Type 必须实现 IntegerLiteralConvertible。

大概有 IntegerLiteralConvertible, FloatLiteralConvertible, StringLiteralConvertible, ArrayLiteralConvertible, DictionaryLiteralConvertible, CharacterLiteralConvertible 等。

_BridgedToObjectiveC _ConditionallyBridgedToObjectiveC

顾名思义。就是相当于这个 Swift 类型可以和某一 Objective-C 类型对等,可以相互转换。

Conditional 多了个判断函数,也就是说这个类型可能是并没有对等起来的,什么情况下用呢,目前猜测应该是带泛型参数的类型中用到,有的泛型可以对应,有的不可以。

1
2
3
4
5
6
protocol _BridgedToObjectiveC {
  typealias ObjectiveCType
  class func getObjectiveCType() -> Any.Type
  func bridgeToObjectiveC() -> ObjectiveCType
  class func bridgeFromObjectiveC(source: ObjectiveCType) -> Self?
}

Reflectable Mirror

这个具体暂时不清楚。反射相关的两个 protocol 。

Sequence Generator

这两个用于 Swift 的 for-in 循环,简单说就是,实现了 Sequence 协议的对象可以 .generate() 出一个 Generator, 然后 Generator 可以不断地 .next() 返回 Type?,其中 Type 是这个序列的泛型。

Hashable Equatable

英汉字典拿来。所以其实为了让 Objective-C 类型在 Swift 代码中正常工作,这个是必不可少的。

第一部分,类型交互

For 类、方法、协议

通过 @objc(name) 转为 Swift 定义。其中 name 为 Objective-C 下的名字。

例如

1
2
3
4
5
6
7
8
9
10
@objc(NSNumber) class NSNumber : NSValue {
    @objc(init) convenience init()
    @objc var integerValue: Int {
        @objc(integerValue) get {}
    }
    ...
}
@objc(NSCopying) protocol NSCopying {
    @objc(copyWithZone:) func copyWithZone(zone: NSZone) -> AnyObject!
}

所有可能为 nil 的指针类型几乎都被转为 Type!,由于 ImplicitlyUnwrappedOptional 的特性,所以几乎用起来一样。

For 基础数字类型

Int, UInt, Float, Double 均实现了 _BridgedToObjectiveC, 其中类型参数 ObjectiveCType 均为 NSNumber

也就是说,可以直接 .getObjectiveCType() 获取到 NSNumber,然后剩下的就很熟悉了。

NSNumber 实现了 FloatLiteralConvertible, IntegerLiteralConvertible,所以其实,也可以直接从 Swift 字面常量获得。

For Bool

实际上 Objective-C 中的 BOOL 是某一数字类型,YES, NO 也分别是 1, 0 。

所以 Swift Bool 实现了 _BridgedToObjectiveC,对应于 NSNumber 类型。

For String

NSString 实现了 StringLiteralConvertible,可以直接通过字面常量获得,同时还有到 String 的隐式类型转换。

String 实现了 _BridgedToObjectiveC 对应于 NSString

1
2
3
extension NSString {
  @conversion func __conversion() -> String
}

这就是为什么官方文档说 Swift 中 String 和 Objective-C 基本是相同操作的。其实都是背后的隐式类型转换。

同时 Foundation 还为 String 扩充了很多方法,发现几个比较有意思的是 ._ns,直接返回 NSString, ._index(Int) 返回 String.Index,等等。

For Array

NSArray 实现了 ArrayLiteralConvertible, 可以从字面常量直接获得。还实现了到 AnyObject[] 的隐式类型转换。

Array<T> 实现了 _ConditionallyBridgedToObjectiveC, 对应于 NSArray.

NSArray 还实现了 Sequence 协议,也就是可以通过 for-in 操作。其中 generate() 返回 NSFastGenerator 类,这个应该是在原有 Foundation 没有的。 当然 .next() 返回 AnyObject?

For Dictionary

NSDictionary 实现了 DictionaryLiteralConvertible, Sequence。同时还实现了到 Dictionary<NSObject, AnyObject> 的隐式类型转换。

Dictionary<KeyType, ValueType> 实现了到 NSDictionary 的隐式类型转换。实现了 _ConditionallyBridgedToObjectiveC 对应于 NSDictionary

其他扩充类型

新类型 NSRange,实现了 _BridgedToObjectiveC,对应于 NSValue,实现了到 Range<Int> 的隐式类型转换。

NSMutableSet NSMutableDictionary NSSet NSMutableArray 均实现了 Sequence 可以 for-in .

第二部分:从 Swift 调用 Objective-C

一般说来,你在 Swift 项目新建 Objective-C 类的时候,直接弹出是否创建 Bridge Header 的窗口,点 YES 就是了,这时候一般多出来个 ProjectName-Bridging-Header.h

如果没有自动的话,这个配置在项目的 Build Settings 中的 Swift Compiler - Code Generation 子项里。

说到底,其实是调用编译命令的 -import-objc-header 参数,后面加上这个 Header 文件。

然后你就可以把你的 Objective-C Class 的 .h 文件都 import 到这个 Herder 文件里了。

所有 Swift 代码都可以直接调用。完全透明,自动生成。

第三部分:从 Objective-C 调用 Swift

头文件是 ProjectName-Swift.h。直接 import 。

不要吝啬你的键盘大量地加入 @objc 属性就是了。

具体实现据我猜测是这样,先 swift 调用 -emit-objc-header -emit-objc-header-path 参数控制生成 Objective-C 的 Header,同时 swift 编译为模块,然后再编译一次。

头文件内容大概是会包含一堆宏定义。然后是 Swift 的类定义等。这里可以看到 Swift 的 mangling 名字。

1
2
3
4
5
6
7
8
9
10
11
SWIFT_CLASS("_TtC5Hello11AppDelegate")
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (nonatomic) UIWindow * window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
- (void)applicationWillResignActive:(UIApplication *)application;
- (void)applicationDidEnterBackground:(UIApplication *)application;
- (void)applicationWillEnterForeground:(UIApplication *)application;
- (void)applicationDidBecomeActive:(UIApplication *)application;
- (void)applicationWillTerminate:(UIApplication *)application;
- (instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end

第四部分:一个 Swift 和 Objective-C 混合项目的编译过程

这里只先考虑一个 Swift 项目使用 Objective-C 代码的情况,这个应该暂时比较多见(使用旧的 MVC 代码,用新的 Swift 创建 ui 一类)。

  • 编译所有 X.swift 文件到 X.o (with -emit-objc-header, -import-objc-header) (其中包含 .swiftmodule 子过程)
    • 由于选项里有 -emit-objc-header,所以之后的 Objective-C 文件可以直接 import 对应的 ProjectName-Swift.h
  • 编译 X.mX.o
  • 链接所有 .o 生成可执行文件

Comments