自从Swift开源并被移植到更多平台之后,一个日益显现的问题就是它需要更多地和C进行混编,调用OS API也好,使用第三方程序库也好。因此,接下来的一个话题就是,从各种基础类型、struct、函数、指针到OC对C的各种扩展,这些语言元素是如何桥接到Swift的呢?这个系列里,我们就通过一些实际的场景来了解Swift和C交互的方式。

在这个系列的绝大部分视频里,我们都会在traditional_oc.m/.h中编写传统的C代码,并在main.swift中访问这些C代码。

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版本,而不要直接使用这些别名对应的原生类型。

导入基本类型的全局变量

例如,我们在traditionial_oc.h中声明一个常量全局变量:

const int global_ten;

并在traditional_oc.m中定义它:

const int global_ten = 10;

这样,global_ten就会作为一个Swift全局常量被引入,我们可以直接读取它的值,但不可以改写:

let ten = global_ten
global_ten = 10.0 // Compile time error: `global_ten` is a constant.

导入NS_STRING_ENUM修饰的类型

在OC代码里,我们经常会定义一组有共同前缀的常量来模拟enum的特性。例如,先在traditional_oc.h中,添加下面的代码:

typedef NSString * TrafficLightColor NS_STRING_ENUM;

TrafficLightColor const TrafficLightColorRed;
TrafficLightColor const TrafficLightColorYellow;
TrafficLightColor const TrafficLightColorGreen;

然后,在traditional_oc.m中,初始化这些全局变量:

TrafficLightColor const TrafficLightColorRed = @"Red";
TrafficLightColor const TrafficLightColorYellow = @"Yellow";
TrafficLightColor const TrafficLightColorGreen = @"Green";

于是,对于用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;

于是,在Swift里,我们就可以这样来使用这些OC常量了:

let redColor: TrafficLightColor = .red
let redColorRawValue = redColor.rawValue // Red

就像我们刚才说过的,NS_STRING_ENUM修饰的类型,通常表示某个范围里,值固定的类型。例如我们不会再期望给它添加个蓝灯这样的属性。但并不是所有的类型都如此,如果一个类型的值有可能扩展,我们可以使用NS_EXTENSIBLE_STRING_ENUM来修饰它。

NS_EXTENSIBLE_STRING_ENUM

例如,在traditional_oc.h中,添加下面的声明:

typedef int Shape NS_EXTENSIBLE_STRING_ENUM;

Shape const ShapeCircle;
Shape const ShapeTriangle;
Shape const ShapeSquare;

并在traditional_oc.m中定义它们:

Shape const ShapeCircle = 1;
Shape const ShapeTriangle = 2;
Shape const ShapeSquare = 3;

这样,按照之前的逻辑,类型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_ENUMNS_EXTENSIBLE_STRING_ENUM并不是什么语言层面上的限制,而只是语义上的差别。面对这种差别,Swift为NS_EXTENSIBLE_STRING_ENUM提供了更为方便的扩展方法罢了。

What's next?

以上,就是这一节的内容。我们首先了解了C中基本类型映射到Swift的方式;并了解了这些基本类型的全局变量桥接到Swift之后的访问方法。下一节,我们来看C中的简单函数是如何桥接到Swift的。

所有订阅均支持 12 期免息分期

¥ 59

按月订阅

一个月,观看并下载所有视频内容。初来泊学,这可能是个最好的开始。

开始订阅

¥ 512

按年订阅

一年的时间,让我们一起疯狂地狩猎知识吧。比按月订阅优惠 28%

开始订阅

¥ 1280

泊学终身会员

永久观看和下载所有泊学网站视频,并赠送 100 元商店优惠券。

我要加入
如需帮助,欢迎通过以下方式联系我们