自从Swift
开源并被移植到更多平台之后,一个日益显现的问题就是它需要更多地和C进行混编,调用OS API也好,使用第三方程序库也好。
因此,接下来的一个话题就是,从各种基础类型
、struct
、函数
、指针
到OC对C的各种扩展,这些语言元素是如何桥接到Swift
的呢?这个系列里,我们就通过一些实际的场景来了解Swift
和C
交互的方式。
Demo 地址:InteroperateSwiftWithC
C语言中的基础类型
首先要介绍的,是C中的基础类型,大家可以在这里
找到完整的基本类型对应表,简单来说,就是对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类型对应的Swift
类型,当和C代码交互的时候,我们也应该总是使用这些类型的typealias
版本,而不要直接使用这些别名对应的原生类型。
traditional_oc.h
#ifndef traditional_oc_h
#define traditional_oc_h
#import <Foundation/Foundation.h>
//导入基本类型的全局变量
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 }
}
这个转换规则是这样的:
根据NS_STRING_ENUM修饰的类型决定导入到Swift时struct的名字,因此,导入的类型名称就是TrafficLightColor;
去掉和类型名称相同的公共前缀,并把剩余部分首字母小写后,变成struct的type property;
*/
let redColor: TrafficLightColor = .red
let redColorRawValue = redColor.rawValue // Red
//这样,按照之前的逻辑,类型Shape在Swift中会被导入成一个struct,它和TrafficLight唯一不同的地方在于,多了一个可以省略参数的init方法,使得我们可以在Swift里,这样扩展Shape的值:
extension Shape {
static var ellipse: Shape {
return Shape(4)
}
}
let e: Shape = .ellipse
//当然,这并不是说使用NS_STRING_ENUM导入的类型就不可以扩展,例如,我们也可以在Swift里,这样扩展TrafficLightColor:
extension TrafficLightColor {
static var blue: TrafficLightColor {
return TrafficLightColor(rawValue: "Blue")
}
}
//从语法上来说,这没有任何问题。因此,NS_STRING_ENUM和NS_EXTENSIBLE_STRING_ENUM并不是什么语言层面上的限制,而只是语义上的差别。面对这种差别,Swift为NS_EXTENSIBLE_STRING_ENUM提供了更为方便的扩展方法罢了。
基本函数是如何做桥接的
在Swift里,add会变成这样:
func add(_ m: Int32, _ n: Int32) -> Int32 {
return m + n
}
其中有两点需要注意:
- C中桥街过来的函数默认都是省略
external name
的; - C中的int会自动转换成
Int32
,因此默认是不能传递Swift Int
类型的,只能使用CInt
类型;
let sum = add(32, 23)
- 它们都是很简单的C代码,如果你还不熟悉C的可变参数函数,可以先在这里简单了解下,我们就不重复了。这两个函数会如何桥接到
Swift
呢?遗憾的是,Swift只能接受vsum
,而不能接受sum
。也就是说,无论如何都无法在Swift中直接调用sum
函数。而vsum
桥接到Swift
之后,是这样的:
func vsum(count: Int32, numbers: CVaListPointer) -> Int32
因此,在
Swift
里,我们不能像vsum
(6, 1, 2, 3, 4, 5, 6)这样调用vsum
,那么这个CVaListPointer
是什么呢?简单来说,它就是C中va_list
桥接到Swift
后对应的类型。为了得到这个对象,我们有两种方法。第一种,是调用
getVaList
方法,并把要传递的可变参数作为一个数组传递给它:
let vaListPointer = getVaList([1, 2, 3, 4, 5, 6])
let sum1 = vsum(6, vaListPointer)
这样,我们就可以把
vaListPointer
作为vsum
的第二个参数了。第二种,是调用
withVaList
方法,它的第一个参数是一个数组,我们像调用getVaList
一样把所有可变参数传递给它;第二个参数是一个closure
,withVaList
会根据第一参数中的所有成员,生成一个对应的CVaListPointer
对象,并传递给这个closure
。因此,我们只要在closure
里调用vsum
就好了:
let sum2 = withVaList([1, 2, 3, 4, 5, 6]) {
vaListPointer in
vsum(6, vaListPointer)
}