自從Swift
開源并被移植到更多平臺之后托启,一個日益顯現(xiàn)的問題就是它需要更多地和C進行混編屯耸,調(diào)用OS API也好,使用第三方程序庫也好。
因此多矮,接下來的一個話題就是塔逃,從各種基礎(chǔ)類型
、struct
、函數(shù)
格粪、指針
到OC對C的各種擴展匀借,這些語言元素是如何橋接到Swift
的呢吓肋?這個系列里,我們就通過一些實際的場景來了解Swift
和C
交互的方式紫新。
Demo 地址:InteroperateSwiftWithC
C語言中的基礎(chǔ)類型
首先要介紹的芒率,是C中的基礎(chǔ)類型偶芍,大家可以在這里
找到完整的基本類型對應(yīng)表匪蟀,簡單來說宰僧,就是對C中的類型采取駝峰式命名之后段化,加上前綴字母C穗泵。例如:
int
變成 CInt
;
unsigned char
變成CUnsignedChar
夷磕;
unsigned long long
變成CUnsignedLongLong
坐桩;
其中,只有有三個表示寬字符的類型是特殊的:
wchat_t
變成CWideChar
膘螟;
char16_t
變成CChar16
;
char32_t
變成CChar32
净当;
于是,在Swift里潭苞,我們可以直接使用這些類型來定義變量此疹,例如:
let cInt: CInt = 10
let cChar: CChar = 49
在Xcode里按住option點擊這些類型就會看到蝗碎,它們都是typealias
衍菱。例如,CInt
的定義是這樣的:
typealias CInt = Int32
但是辫呻,即便我們知道了這些C類型對應(yīng)的Swift
類型放闺,當(dāng)和C代碼交互的時候怖侦,我們也應(yīng)該總是使用這些類型的typealias
版本,而不要直接使用這些別名對應(yīng)的原生類型。
traditional_oc.h
#ifndef traditional_oc_h
#define traditional_oc_h
#import <Foundation/Foundation.h>
//導(dǎo)入基本類型的全局變量
const int global_ten;
//NS_STRING_ENUM修飾的類型艳悔,通常表示某個范圍里猜年,值固定的類型
typedef NSString * TrafficLightColor NS_STRING_ENUM;
TrafficLightColor const TrafficLightColorRed;
TrafficLightColor const TrafficLightColorYellow;
TrafficLightColor const TrafficLightColorGreen;
//如果一個類型的值有可能擴展,我們可以使用`NS_EXTENSIBLE_STRING_ENUM`來修飾它
typedef int Shape NS_EXTENSIBLE_STRING_ENUM;
Shape const ShapeCircle;
Shape const ShapeTriangle;
Shape const ShapeSquare;
int add(int m, int n);
int sum(int count, ...);
int vsum(int count, va_list numbers);
#endif
traditional_oc.m
#import "traditional_oc.h"
const int global_ten = 10;
TrafficLightColor const TrafficLightColorRed = @"Red";
TrafficLightColor const TrafficLightColorYellow = @"Yellow";
TrafficLightColor const TrafficLightColorGreen = @"Green";
Shape const ShapeCircle = 1;
Shape const ShapeTriangle = 2;
Shape const ShapeSquare = 3;
int add(int m, int n) {
return m + n;
}
int sum(int count, ...) {
va_list ap;
int s = 0;
va_start(ap, count);
vsum(count, ap);
va_end(ap);
return s;
}
int vsum(int count, va_list numbers) {
int s = 0;
int i = 0;
for (; i < count; ++i) {
s += va_arg(numbers, int);
}
return s;
}
main.swift
import Foundation
let ten = global_ten
/*
對于用NS_STRING_ENUM修飾的TrafficLightColor,引入到Swift就會變成一個類似這樣的struct:
struct TrafficLightColor: RawRepresentable {
typealias RawValue = String
init(rawValue: RawValue)
var rawValue: RawValue { get }
static var red: TrafficLightColor { get }
static var yellow: TrafficLightColor { get }
static var green: TrafficLightColor { get }
}
這個轉(zhuǎn)換規(guī)則是這樣的:
根據(jù)NS_STRING_ENUM修飾的類型決定導(dǎo)入到Swift時struct的名字,因此,導(dǎo)入的類型名稱就是TrafficLightColor;
去掉和類型名稱相同的公共前綴莲组,并把剩余部分首字母小寫后锹杈,變成struct的type property;
*/
let redColor: TrafficLightColor = .red
let redColorRawValue = redColor.rawValue // Red
//這樣邪码,按照之前的邏輯闭专,類型Shape在Swift中會被導(dǎo)入成一個struct影钉,它和TrafficLight唯一不同的地方在于平委,多了一個可以省略參數(shù)的init方法夺谁,使得我們可以在Swift里匾鸥,這樣擴展Shape的值:
extension Shape {
static var ellipse: Shape {
return Shape(4)
}
}
let e: Shape = .ellipse
//當(dāng)然扫腺,這并不是說使用NS_STRING_ENUM導(dǎo)入的類型就不可以擴展,例如,我們也可以在Swift里躁劣,這樣擴展TrafficLightColor:
extension TrafficLightColor {
static var blue: TrafficLightColor {
return TrafficLightColor(rawValue: "Blue")
}
}
//從語法上來說账忘,這沒有任何問題。因此溉浙,NS_STRING_ENUM和NS_EXTENSIBLE_STRING_ENUM并不是什么語言層面上的限制戳稽,而只是語義上的差別。面對這種差別,Swift為NS_EXTENSIBLE_STRING_ENUM提供了更為方便的擴展方法罷了颂郎。
基本函數(shù)是如何做橋接的
在Swift里乓序,add會變成這樣:
func add(_ m: Int32, _ n: Int32) -> Int32 {
return m + n
}
其中有兩點需要注意:
- C中橋街過來的函數(shù)默認都是省略
external name
的; - C中的int會自動轉(zhuǎn)換成
Int32
房维,因此默認是不能傳遞Swift Int
類型的抬纸,只能使用CInt
類型;
let sum = add(32, 23)
- 它們都是很簡單的C代碼膜蛔,如果你還不熟悉C的可變參數(shù)函數(shù)墅茉,可以先在這里簡單了解下呜呐,我們就不重復(fù)了。這兩個函數(shù)會如何橋接到
Swift
呢?遺憾的是洋魂,Swift只能接受vsum
,而不能接受sum
庄岖。也就是說顿锰,無論如何都無法在Swift中直接調(diào)用sum
函數(shù)启搂。而vsum
橋接到Swift
之后,是這樣的:
func vsum(count: Int32, numbers: CVaListPointer) -> Int32
因此,在
Swift
里疑苫,我們不能像vsum
(6, 1, 2, 3, 4, 5, 6)這樣調(diào)用vsum
撼短,那么這個CVaListPointer
是什么呢挺勿?簡單來說不瓶,它就是C中va_list
橋接到Swift
后對應(yīng)的類型蚊丐。為了得到這個對象,我們有兩種方法孽椰。第一種黍匾,是調(diào)用
getVaList
方法膀捷,并把要傳遞的可變參數(shù)作為一個數(shù)組傳遞給它:
let vaListPointer = getVaList([1, 2, 3, 4, 5, 6])
let sum1 = vsum(6, vaListPointer)
這樣全庸,我們就可以把
vaListPointer
作為vsum
的第二個參數(shù)了壶笼。第二種,是調(diào)用
withVaList
方法,它的第一個參數(shù)是一個數(shù)組责语,我們像調(diào)用getVaList
一樣把所有可變參數(shù)傳遞給它坤候;第二個參數(shù)是一個closure
胁赢,withVaList
會根據(jù)第一參數(shù)中的所有成員,生成一個對應(yīng)的CVaListPointer
對象徒河,并傳遞給這個closure
顽照。因此棒厘,我們只要在closure
里調(diào)用vsum
就好了:
let sum2 = withVaList([1, 2, 3, 4, 5, 6]) {
vaListPointer in
vsum(6, vaListPointer)
}