我们知道在Swift
中,可以在NSArray
与Array
之间做无缝的转换,如下所示:
|
|
编译器会为了我们完成所有转换,我们只需要拿来即用就行。当然,除了数组外,还有字典(Dictionary
)、集合(Set
)、字符串(String
)也是一样。
问题
不过仔细一想,会发现,NSArray
是类类型,而Array
是结构体类型。一个是引用类型,一个是值类型,它们是怎样实现无缝转换的呢?这让我们想到了Cocoa Foundation
与Core Foundation
之间转换的toll-free bridging
技术。那NSArray
与Array
之间是不是也应该有类似的桥接实现呢?
Objective-C Bridge
我们将鼠标移动到Array
上,然后"cmd+鼠标点击"
,进入到Swift
的声明文件中,在Array
的注释中,可以看到下面这段:
|
|
可以看到Array
与Objective-C
的数组之间确实存在某种桥接技术,我们暂且称之为"Objective-C Bridge"
桥接。那这又是如何实现的呢?
我们在当前文件中搜索bridge
,会发现有这样一个协议:_ObjectiveCBridgeable
。我们先来看看它的声明:
|
|
即一个Swift
数组或字典,如果其元素类型实现了_ObjectiveCBridgeable
协议,则该数组或字典可以被转换成Objective-C
的数组或字典。对于_ObjectiveCBridgeable
协议,我们目前所能得到的文档就只有这些,也看不到它里面声明了什么属性方法。不过,可以看到这个协议是访问控制权限是public
,也就意味着可以定义类来实现这个接口。这就好办了,下面就来尝试实现这样一个转换。
Objective-C类与Swift结构体的互转示例
在此先定义一个Objective-C
类,如下所示:
|
|
同样,我定义一个Swift
结构体,如下所示:
|
|
要想实现Mobile
类与SwiftMobile
结构体之间的互转,则SwiftMobile
结构体需要实现_ObjectiveCBridgeable
协议,如下所示:
|
|
可以看到SwiftMobile
结构体主要实现了_ObjectiveCBridgeable
接口的5
个方法,从方法名基本上就能知道每个方法的用途。这里需要注意的是在本例中的_conditionallyBridgeFromObjectiveC
只是简单地调用了_forceBridgeFromObjectiveC
,如果需要指定条件,则需要更详细的实现。
让我们来测试一下:
|
|
可以看到只需要使用as
,就能实现Mobile
类与SwiftMobile
结构体的无缝转换。是不是很简单?
集合类型的无缝互换
回到数组的议题上来。
我们知道NSArray
的元素类型必须是类类型的,它不支持存储结构体、数值等类型。因此,Array
转换成NSArray
的前提是Array
的元素类型能被NSArray
所接受。如果存储在Array
中的元素的类型是结构体,且该结构体实现了_ObjectiveCBridgeable
接口,则转换成NSArray
时,编译器会自动将所有的元素转换成对应的类类型对象。以上面的SwiftMobile
为例,看如下代码:
|
|
可以看到打印mobileArray
数组时,其元素已经转换成了类Mobile
的对象。一切都是那么的自然。而如果我们的SwiftMobile
并没有实现_ObjectiveCBridgeable
接口,则会报编译器错误:
|
|
实际上,像Bool
,Int
, UInt
,Float
,Double
,CGFloat
这些数值类型也实现了_ObjectiveCBridgeable
接口。我们可以从文档OS X v10.11 API Diffs - Swift Changes for Swift中找到一些线索:
|
|
(注意:整型类型只有Int
与UInt
实现了接口,而其它诸如Int16
,Uint32
,Int8
等则没有)
它们的目标类型都是NSNumber
类型,如下代码所示:
|
|
当然,要想实现Array
与NSArray
无缝切换,除了元素类型需要支持这种操作外,Array
本身也需要能支持Objective-C Bridge
,即它也需要实现_ObjectiveCBridgeable
接口。在Swift
文件的Array
声明中并没有找到相关的线索:
|
|
线索依然在OS X v10.11 API Diffs - Swift Changes for Swift中,有如下声明:
|
|
因此,Array
与NSArray
相互转换需要两个条件:
Array
自身实现Objective-C Bridge
桥接,这个Swift
已经帮我们实现了。Array
中的元素如果是数值类型或结构类型,必须实现Objective-C Bridge
桥接。而如果是类类型或者是@objc protocol
类型,则不管这个类型是Objective-C
体系中的,还是纯Swift
类型(不继承自NSObject
),都可以直接转换。
另外,Array
只能转换成NSArray
,而不能转换成NSArray
的子类,如NSMutableArray
或NSOrderedArray
。如下所示:
|
|
当然,反过来却是可以的。这个应该不需要太多的讨论。
小结
在Swift
中,我们更多的会使用Array
,Dictionary
,Set
这几个集合类型来存储数据,当然也会遇到需要将它们与Objective-C
中对应的集合类型做转换的情况,特别是在混合编程的时候。另外,String
也是可能经常切换的一个地方。不过,Apple
已经帮我们完成了大部分的工作。如果需要实现自定义的结构体类型与Objective-C
类的切换,则可以让结构体实现_ObjectiveCBridgeable
接口。
这里还有个小问题,在Objective-C
中实际上是有两个类可以用来包装数值类型的值:NSNumber
与NSValue
。NSNumber
我们就不说了,NSValue
用于包装诸如CGPoint
,CGSize
等,不过Swift
并没有实现CGPoint
类的值到NSValue
的转换,所以这个需要我们自己去处理。
在Swift
与Objective-C
的集合类型相互转换过程中,还涉及到一些性能问题,大家可以看看对应的注释说明。在后续的文章中,会涉及到这一主题。
本文的部分实例代码已上传至github
,可以在这里下载。