原文鏈接
我們知道在Swift
中,可以在NSArray
與Array
之間做無縫的轉換,如下所示:
1
2
3
4
5
6
7
8
9
10
11
|
let mobile = ["iPhone", "Nokia", "小米Note"]
let mobile1 = (mobile as NSArray).objectAtIndex(1)
print(mobile1)
let animalArray = NSArray(objects: "lion", "tiger", "monkey")
var animalCount = (animalArray as Array).count
print(animalCount)
// 輸出
// "Nokia"
// ["lion", "tiger", "monkey"]
|
編譯器會爲了我們完成所有轉換,我們只需要拿來即用就行。當然,除了數組外,還有字典(Dictionary
)、集合(Set
)、字符串(String
)也是一樣。
問題
不過仔細一想,會發現,NSArray
是類類型,而Array
是結構體類型。一個是引用類型,一個是值類型,它們是怎樣實現無縫轉換的呢?這讓我們想到了Cocoa
Foundation
與Core
Foundation
之間轉換的toll-free
bridging
技術。那NSArray
與Array
之間是不是也應該有類似的橋接實現呢?
Objective-C Bridge
我們將鼠標移動到Array
上,然後"cmd+鼠標點擊"
,進入到Swift
的聲明文件中,在Array
的註釋中,可以看到下面這段:
1
2
3
4
5
6
|
/// Objective-C Bridge
/// ==================
/// The main distinction between Array and the other array types is that it interoperates seamlessly and efficiently with Objective-C.
/// Array<Element> is considered bridged to Objective-C iff Element is bridged to Objective-C.
// ......
|
可以看到Array
與Objective-C
的數組之間確實存在某種橋接技術,我們暫且稱之爲"Objective-C
Bridge"
橋接。那這又是如何實現的呢?
我們在當前文件中搜索bridge
,會發現有這樣一個協議:_ObjectiveCBridgeable
。我們先來看看它的聲明:
1
2
3
|
/// A Swift Array or Dictionary of types conforming to `_ObjectiveCBridgeable` can be passed to Objective-C as an NSArray or NSDictionary, respectively. The elements of the resulting NSArray or NSDictionary will be the result of calling `_bridgeToObjectiveC` on each element of the source container.
public protocol _ObjectiveCBridgeable {
}
|
即一個Swift
數組或字典,如果其元素類型實現了_ObjectiveCBridgeable
協議,則該數組或字典可以被轉換成Objective-C
的數組或字典。對於_ObjectiveCBridgeable
協議,我們目前所能得到的文檔就只有這些,也看不到它裏面聲明瞭什麼屬性方法。不過,可以看到這個協議是訪問控制權限是public
,也就意味着可以定義類來實現這個接口。這就好辦了,下面就來嘗試實現這樣一個轉換。
Objective-C類與Swift結構體的互轉示例
在此先定義一個Objective-C
類,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
// Mobile.h
@interface Mobile : NSObject
@property (nonatomic, strong) NSString *brand;
@property (nonatomic, strong) NSString *system;
- (instancetype)initWithBrand:(NSString *)brand system:(NSString *)system;
@end
// Mobole.m
@implementation Mobile
- (instancetype)initWithBrand:(NSString *)brand system:(NSString *)system {
self = [super init];
if (self) {
_brand = brand;
_system = system;
}
return self;
}
@end
|
同樣,我定義一個Swift
結構體,如下所示:
1
2
3
4
5
|
struct SwiftMobile {
let brand: String
let system: String
}
|
要想實現Mobile
類與SwiftMobile
結構體之間的互轉,則SwiftMobile
結構體需要實現_ObjectiveCBridgeable
協議,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
extension SwiftMobile: _ObjectiveCBridgeable {
typealias _ObjectiveCType = Mobile
// 判斷是否能轉換成Objective-C對象
static func _isBridgedToObjectiveC() -> Bool {
return true
}
// 獲取轉換的目標類型
static func _getObjectiveCType() -> Any.Type {
return _ObjectiveCType.self
}
// 轉換成Objective-C對象
func _bridgeToObjectiveC() -> _ObjectiveCType {
return Mobile(brand: brand, system: system)
}
// 強制將Objective-C對象轉換成Swift結構體類型
static func _forceBridgeFromObjectiveC(source: _ObjectiveCType, inout result: SwiftMobile?) {
result = SwiftMobile(brand: source.brand, system: source.system)
}
// 有條件地將Objective-C對象轉換成Swift結構體類型
static func _conditionallyBridgeFromObjectiveC(source: _ObjectiveCType, inout result: SwiftMobile?) -> Bool {
_forceBridgeFromObjectiveC(source, result: &result)
return true
}
}
|
可以看到SwiftMobile
結構體主要實現了_ObjectiveCBridgeable
接口的5
個方法,從方法名基本上就能知道每個方法的用途。這裏需要注意的是在本例中的_conditionallyBridgeFromObjectiveC
只是簡單地調用了_forceBridgeFromObjectiveC
,如果需要指定條件,則需要更詳細的實現。
讓我們來測試一下:
1
2
3
4
5
6
7
8
9
10
11
12
|
let mobile = Mobile(brand: "iPhone", system: "iOS 9.0")
let swiftMobile = mobile as SwiftMobile
print("\(swiftMobile.brand): \(swiftMobile.system)")
let swiftMobile2 = SwiftMobile(brand: "Galaxy Note 3 Lite", system: "Android 5.0")
let mobile2 = swiftMobile2 as Mobile
print("\(mobile2.brand): \(mobile2.system)")
// 輸出:
// iPhone: iOS 9.0
// Galaxy Note 3 Lite: Android 5.0
|
可以看到只需要使用as
,就能實現Mobile
類與SwiftMobile
結構體的無縫轉換。是不是很簡單?
集合類型的無縫互換
回到數組的議題上來。
我們知道NSArray
的元素類型必須是類類型的,它不支持存儲結構體、數值等類型。因此,Array
轉換成NSArray
的前提是Array
的元素類型能被NSArray
所接受。如果存儲在Array
中的元素的類型是結構體,且該結構體實現了_ObjectiveCBridgeable
接口,則轉換成NSArray
時,編譯器會自動將所有的元素轉換成對應的類類型對象。以上面的SwiftMobile
爲例,看如下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
let sm1 = SwiftMobile(brand: "iPhone", system: "iOS 9.0")
let sm2 = SwiftMobile(brand: "Galaxy Note 3", system: "Android 5.0")
let sm3 = SwiftMobile(brand: "小米", system: "Android 4.0")
let mobiles = [sm1, sm2, sm3]
let mobileArray = mobiles as NSArray
print(mobileArray)
for i in 0..<mobiles.count {
print("\(mobileArray.objectAtIndex(i).brand): \(mobileArray.objectAtIndex(i).system)")
}
// 輸出:
// (
// "<Mobile: 0x100c03f30>",
// "<Mobile: 0x100c03940>",
// "<Mobile: 0x100c039c0>"
// )
// iPhone: iOS 9.0
// Galaxy Note 3: Android 5.0
// 小米: Android 4.0
|
可以看到打印mobileArray
數組時,其元素已經轉換成了類Mobile
的對象。一切都是那麼的自然。而如果我們的SwiftMobile
並沒有實現_ObjectiveCBridgeable
接口,則會報編譯器錯誤:
1
|
'[SwiftMobile]' is not convertible to 'NSArray'
|
實際上,像Bool
,Int
, UInt
,Float
,Double
,CGFloat
這些數值類型也實現了_ObjectiveCBridgeable
接口。我們可以從文檔OS
X v10.11 API Diffs – Swift Changes for Swift中找到一些線索:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
extension Bool : _ObjectiveCBridgeable {
init(_ number: NSNumber)
}
extension Int : _ObjectiveCBridgeable {
init(_ number: NSNumber)
}
extension Float : _ObjectiveCBridgeable {
init(_ number: NSNumber)
}
// ... Double, UInt ...
|
(注意:整型類型只有Int
與UInt
實現了接口,而其它諸如Int16
,Uint32
,Int8
等則沒有)
它們的目標類型都是NSNumber
類型,如下代碼所示:
1
2
3
4
5
6
7
|
let numbers = [1, 29, 40]
let numberArray = (numbers as NSArray).objectAtIndex(2)
print(numberArray.dynamicType)
// 輸出:
// __NSCFNumber
|
當然,要想實現Array
與NSArray
無縫切換,除了元素類型需要支持這種操作外,Array
本身也需要能支持Objective-C
Bridge
,即它也需要實現_ObjectiveCBridgeable
接口。在Swift
文件的Array
聲明中並沒有找到相關的線索:
1
|
public struct Array<Element> : CollectionType, Indexable, SequenceType, MutableCollectionType, MutableIndexable, _DestructorSafeContainer
|
線索依然在OS X v10.11 API Diffs – Swift Changes for Swift中,有如下聲明:
1
2
3
|
extension Array : _ObjectiveCBridgeable {
init(_fromNSArray source: NSArray, noCopy noCopy: Bool = default)
}
|
因此,Array
與NSArray
相互轉換需要兩個條件:
-
Array
自身實現Objective-C
Bridge
橋接,這個Swift
已經幫我們實現了。 -
Array
中的元素如果是數值類型或結構類型,必須實現Objective-C
Bridge
橋接。而如果是類類型或者是@objc
protocol
類型,則不管這個類型是Objective-C
體系中的,還是純Swift
類型(不繼承自NSObject
),都可以直接轉換。
另外,Array
只能轉換成NSArray
,而不能轉換成NSArray
的子類,如NSMutableArray
或NSOrderedArray
。如下所示:
1
2
3
4
5
|
var objects = [NSObject(), NSObject(), NSObject()]
var objectArray = objects as NSMutableArray
// 編譯器錯誤:
// '[NSObject]' is not convertible to 'NSMutableArray'
|
當然,反過來卻是可以的。這個應該不需要太多的討論。
小結
在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
,可以在這裏下載。
參考
-
Easy Cast With _ObjectiveCBridgeable
-
OS
X v10.11 API Diffs – Swift Changes for Swift