C中的基本类型和基本函数在Swift中是如何表示的

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

Demo 地址:InteroperateSwiftWithC

C语言中的基础类型

首先要介绍的,是C中的基础类型,大家可以在这里
找到完整的基本类型对应表,简单来说,就是对C中的类型采取驼峰式命名之后,加上前缀字母C。例如:

CType SwiftType.png

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一样把所有可变参数传递给它;第二个参数是一个closurewithVaList会根据第一参数中的所有成员,生成一个对应的CVaListPointer对象,并传递给这个closure。因此,我们只要在closure里调用vsum就好了:

let sum2 = withVaList([1, 2, 3, 4, 5, 6]) {
    vaListPointer in
    vsum(6, vaListPointer)
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容