我们先来了解C中的简单struct以及处理struct类型的函数是如何桥接到Swift的。所谓简单struct是指不包含任何指针成员的struct,任何与指针有关的话题,稍后我们会在专门的内容中分享。

Structures

为了观察桥接的效果,我们先在traditional_oc.h中,声明一个struct Location

struct Location {
    double x;
    double y;
};

typedef struct Location Location;

这样,Swift就会导入一个同名的struct,其中:

  • 包含C struct中的每一个同名数据成员;
  • 包含两个init方法,一个是默认init,用于把Swift中的每个属性默认初始化。另一个,是memberwise init,它使用属性名称作为init的internal/external name,用于按成员初始化;

因此,Swift导入的struct看上去是这样的:

struct Location {
    var x: Double
    var y: Double

    init()
    init(x: Double, y: Double)
}

我们可以用下面两种方式来定义Location对象,它们分别使用了默认初始化以及memberwise初始化:

var origin = Location()
var eleven = Location(x: 1, y: 1)

除了这些最基本的定义之外,通常一个C struct还会带有一系列用于操作这个类型的全局函数。我们可以使用CF_SWIFT_NAME宏,把这些函数导入成对应Swift类型的extension。例如,在traditional_oc.h中,声明下面这两个方法:

Location moveX(Location loc, double delta);
Location moveY(Location loc, double delta);

然后,在objective_oc.m中定义它们:

Location moveX(Location loc, double delta) {
    loc.x += delta;

    return loc;
}

Location moveY(Location loc, double delta) {
    loc.y += delta;

    return loc;
}

此时,moveXmoveY就会作为全局函数桥接到Swift,我们只能这样调用它们:

var pos = Location()
pos = moveX(pos, 11)

使用CF_SWIFT_NAME导入普通全局函数

但显然,明确给moveX传递pos并不是Swift的风格,moveX应该是Location的一个方法,为此,我们可以把moveXmoveY的声明修改成这样:

Location moveX(Location loc, double delta) CF_SWIFT_NAME(Location.moveX(self:delta:));
Location moveY(Location loc, double delta) CF_SWIFT_NAME(Location.moveY(self:delta:));

其中,CF_SWIFT_NAME中方法的写法,和Swift中#selector的参数是一样的。这样,Swift就会把上面两个方法导入到extension Location里:

extension Location {
    func moveX(delta: Double) -> Location {
        return Location(x: self.x + delta, y: self.y)
    }

    func moveY(delta: Double) -> Location {
        return Location(x: self.x, y: self.y + delta)
    }
}

然后,我们就可以像这样调用moveXmoveY了:

var pos = Location()
pos = pos.moveX(delta: 10)

使用CF_SWIFT_NAME导入init方法

在C中,除了修改struct的函数之外,还有直接创建struct的工厂方法,例如,我们添加一个在y=x直线上创建Location的方法:

// In traditional_oc.h
Location createWithXY(double xy);

// In traditional_oc.m
Location createWithXY(double xy) {
    Location p = { .x = xy, .y = xy };
    return p;
}

这种方法,桥接到Swift后,也不应该是全局函数,而应该是Location的某种init方法。为此,我们同样可以使用CF_SWIFT_NAME来修饰它:

Location createWithXY(double xy) CF_SWIFT_NAME(Location.init(xy:));

然后,就可以这样创建Location对象了:

var eleven = Location(xy: 11)

使用CF_SWIFT_NAME导入type property

除了使用CF_SWIFT_NAME导入方法之外,我们还可以给Location导入type property。例如,假设在C中有一个生成原点位置的方法:

// In traditional_oc.h
Location getOrigin(void);

// In traditional_oc.m
Location origin = { .x = 0, .y = 0 };

Location getOrigin(void) {
    return origin;
}

除了用刚才的方法把getOrigin导入成一个方法,我们还可以这样:

Location getOrigin(void) CF_SWIFT_NAME(getter:Location.origin());

此时,Location在Swift中的定义就会多出一个type property:

extension Location {
    static var origin: Location { get }
}

但我们只能读取它的值,而不能改写它:

let origin = Location.origin // OK (0.0, 0.0)
Locatioin.origin.x = 11 // Compile time error

为了可以修改origin,我们还要再定义一个setter:

// In traditional_oc.h
Location setOrigin(Location newOrigin) CF_SWIFT_NAME(setter:Location.origin(newOrigin:));

// In traditional_oc.m
Location setOrigin(Location newOrigin) {
    origin = newOrigin;

    return origin;
}

这样,无论是赋值新对象,还是修改origin的属性,都可以正常工作了。

Location.origin = Location() // OK
Location.origin.x = 11; // OK

Unions是如何桥接到Swift的?

了解了Swift导入C struct的方式之后,我们来看一种特殊的C结构:union。由于Swift不支持原生的union,因此,C union导入到Swift后,会变成一个struct。来看下面这个例子:

// In traditional_oc.h
union ASCII {
    char character; // ASCII character
    int code;       // ASCII code
};

typedef union ASCII ASCII;

为了可以在Swift里表达ASCII可以被charint类型的值初始化,ASCII导入到Swift后会带有两个init方法:

struct ASCII {
    init(character: Int8)
    init(code: Int32)
}

然后,为了可以让ASCII对象可以分别读取到这两个值,Swift还会为ASCII生成两个属性:

struct ASCII {
    var character: Int8
    var code: Int32
}

因此,我们就可以像下面这样使用ASCII类型了:

let a = ASCII(character: Int8("a".utf8["a".startIndex]))

print(a.character)

需要注意的是,调用的ASCII.init方法,必须和访问的属性对应。例如在上面的例子里,如果我们访问a.code,就会发生运行时错误。

匿名的struct和union数据成员是如何桥接到Swift的?

了解了structunion桥接到Swift的方法后,我们来看一个特殊的情况,在struct的定义里,structunion类型的数据成员可以是匿名的。例如下面这个例子:

// In traditional_oc.h
struct Car {
    union {
        char model;
        int series;
    };

    struct {
        double pricing;
        bool isAvailable;
    } info;
};

typedef struct Car Car;

其中,Car包含了两个成员:

  • 首先,是匿名的union,表示车既可以用一个字母表示型号,也可以用数字表示系列;
  • 其次,是个具名struct,包含了汽车的售价以及是否有货;

之前我们说过,C struct导入Swift之后,会创建一个默认的memberwise init方法,但是union并没有名称,我们该如何初始化呢?来看下面这段Swift代码:

let bmw = Car(.init(series: 5),
    info: .init(price: 500000, isAvailable: true))

可以看到,Swift不会为匿名的union生成external name,我们直接调用.init,type inference会推导出这里需要的类型,我们只要按照之前讲过的方式初始化union就好了。

初始化完成后,我们可以直接访问union的数据成员,就像匿名的类型不存在一样:

print(bmw.series) // 5

What's next?

以上,就是C中的structunion桥接到Swift的方式,尽管我们还没有提及指针类型,但它已经比之前的简单类型复杂一些了。下一节,我们来看enum是如何桥接到Swift的。

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

¥ 59

按月订阅

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

开始订阅

¥ 512

按年订阅

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

开始订阅

¥ 1280

泊学终身会员

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

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