0%

Swift 与C语言交互

与C语言交互发生在使用一些C语言编写的API上。C语言的语法会桥接到Swift中对应的语法中。Swift能很好地与C语言交互。

类型

C语言的基础类型、枚举、结构体、联合体在Swift中都有对应。

其中基础类型是一一对等的。其命名方式对C中的类型采取驼峰式命名之后,加上前缀字母C。

例如:

  • int 变成 CInt
  • unsigned char 变成 CUnsignedChar
  • unsigned long long 变成 CUnsignedLongLong

其中,只有有三个表示宽字符的类型是特殊的:

  • wchat_t 变成 CWideChar
  • char16_t 变成 CChar16
  • char32_t 变成 CChar32

这些在Swift对应的基础类型都是typealias,在Swift应应使用typealias类型而不直接使用原生类型。

变量

全局变量/常量也同样映射到Swift的全局变量/常量中。

枚举、结构体、联合体

C中的枚举只是一个普通的基础常量,除非使用NS_×_ENUMNS_OPTIONS关键字修饰来定义枚举才会映射到Swift中的结构体,但基本使用跟Swift的枚举无异。

结构体则可以直接映射到Swift的结构体。

联合体则是映射到一个结构体中。

函数

C语言的函数基本能映射到Swift的函数中,除了不支持不固定参数函数,作为替代方案,Swift支持映射va_list表示的可变参数列表。

使用CF_SWIFT_NAME还能把C函数重命名甚至并入extension中。

指针

Swift的指针都带有Unsafe关键字,表示其使用在编译期可能是不可预测的。

Apple Developer 文档里有 C 指针和 Swift 指针的对应表:

C Syntax Swift Syntax
const Type * UnsafePointer
Type * UnsafeMutablePointer
Type * const * UnsafePointer
Type * __strong * UnsafeMutablePointer
Type ** AutoreleasingUnsafeMutablePointer
const void * UnsafeRawPointer
void * UnsafeMutableRawPointer

除此以外,Swift还有几种指针表达方式:

  • UnsafeBufferPointer、UnsafeMutableBufferPointer、UnsafeRawBufferPointer、UnsafeMutableRawBufferPointer
  • OpaquePointer

Buffer

它相当于在原始内存空间上添加一个view,以步幅(MemoryLayout<T>.stride)单位,以集合的方式访问底层内存。对应C语言的就是数组的访问。

OpaquePointer

对于在C回调函数中要传递Swift的对象时,这些对象可能无法桥接到C的类型,这时就要用到OpaquePointer。

转换Swift对象为指针需要用到Unmanaged。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var fooObj = Foo()
// 创建Unmanaged<Foo>实例
let unmanagedFoo = Unmanaged.passRetained(fooObj)
// 转换成OpaquePointer,该指针可直接传递到void *的C指针中
let unmanagedPtr = unmanagedFoo.toOpaque()

// 传递到C函数
aFuncWithCallback(unmanagedPtr) {
(ptr: UnsafeMutableRawPointer?) -> Void in
// 创建Unmanaged<Foo>,并转换为Foo类型
let fooObj =
Unmanaged<Foo>.fromOpaque(ptr!).takeUnretainedValue()
print(fooObj.foo)
}

Unmanaged创建和转换都有Retained和Unretained版本,对应的是指是否引用计数操作。Pass方法是否+1引用计数;take方法是否-1引用计数。

字符串指针

C中表示字符串的 char *,桥接到Swift会变成 UnsafeMutablePointer<Int8>,可以通过相应的构造方法创建String类型。

指针转换

方法参数是指针

调用将指针作为参数的函数时,可以使用隐式转换来传递兼容的指针类型或使用隐式桥接来传递指向变量或数组内容的指针。

若是常量指针参数(UnsafePointer<Type>),可以直接传递:字符串、指定类型数组、指定类型的inout表达式。

若是可变指针参数(UnsafeMutablePointer<Type>),可以直接传递:指定类型数组的inout表达式、指定类型的inout表达式。

向下隐式转换

指针在作为函数参数传递时,可以隐式转换:

  • 不可变指针 -> 可变指针
  • 类型指针 -> 原始指针

Raw可以直接兼容没有Raw的类型指针。如:需要UnsafeRawPointer类型参数时,可以直接传递UnsafePointer<Type>

隐式桥接

类型变量/常量、数组、字符串,在传递给指针参数时会进行隐式桥接。

Swift基本类型 -> 指针

即用指针访问Swift变量/常量。

使用withUnsafexxxPointer函数,在闭包中用指针临时访问指向的变量/常量。

注意:在闭包中得到的指针不要返回出去。因为这是随外部变量/常量的生命周期影响,会因为其变量/常量销毁而变成野指针。

顶级函数:

1
2
3
4
5
6
7
// 指向具体类型变量的类型指针
func withUnsafePointer<T, Result>(to value: inout T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
func withUnsafeMutablePointer<T, Result>(to value: inout T, _ body: (UnsafeMutablePointer<T>) throws -> Result) rethrows -> Result

// 以字节为单位访问,注意闭包中的参数是Buffer类型
func withUnsafeBytes<T, Result>(of value: inout T, _ body: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result
func withUnsafeMutableBytes<T, Result>(of value: inout T, _ body: (UnsafeMutableRawBufferPointer) throws -> Result) rethrows -> Result

数组中的应用

  • withUnsafeByteswithUnsafeMutableBytes
  • withUnsafeBufferPointerwithUnsafeMutableBufferPointer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// withUnsafeMutableBytes应用
var numbers: [Int32] = [0, 0]
var byteValues: [UInt8] = [0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00]

numbers.withUnsafeMutableBytes { destBytes in
byteValues.withUnsafeBytes { srcBytes in
destBytes.copyBytes(from: srcBytes)
}
}
// numbers == [1, 2]

// withUnsafeBytes应用
var numbers = [1, 2, 3]
var byteBuffer: [UInt8] = []
numbers.withUnsafeBytes {
byteBuffer.append(contentsOf: $0)
}
// byteBuffer == [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, ...]

// withUnsafeBufferPointer应用
let numbers = [1, 2, 3, 4, 5]
let sum = numbers.withUnsafeBufferPointer { buffer -> Int in
var result = 0
for i in stride(from: buffer.startIndex, to: buffer.endIndex, by: 2) {
result += buffer[i]
}
return result
}
// 'sum' == 9

// withUnsafeMutableBufferPointer应用
var numbers = [1, 2, 3, 4, 5]
numbers.withUnsafeMutableBufferPointer { buffer in
for i in stride(from: buffer.startIndex, to: buffer.endIndex - 1, by: 2) {
buffer.swapAt(i, i + 1)
}
}
print(numbers)
// Prints "[2, 1, 4, 3, 5]"

字符串中的应用

  • withCString
  • withUTF8

Data中的应用

  • withUnsafeBytes
  • withUnsafeMutableBytes

指针类型转换

注意Swift中的指针类型不存在继承关系。

类型指针 -> 原始指针

直接用原始指针的构造方法进行转换,如果是作为入参,则无需转换直接传递即可。

原始指针 -> 类型指针

永久绑定:bindMemoryassumingMemoryBound,两者操作的类型必须一致。

这种方式需要从原始指针调用,如果是类型指针要转换类型,则须转换为原始指针,再调用绑定类型方法。bindMemory会导致原来的类型指针是未定义的,如果修改后兼容原来。

临时转换访问:withMemoryRebound,该方法的访问发生在闭包参数中。

访问指向的值

若是类型指针(UnsafePointer<Type>),可直接访问pointee,可读写。

若是原始指针(UnsafeRawPointer),使用load方法,返回指定偏移的具体类型。只读。

当然对于可变原始指针(UnsafeMutableRawPointer),有对应的写入方法:

  • copyMemory:从其他原始指针拷贝字节数据
  • storeBytes:写入具体类型

C指针使用注意

在 Core Foundation 里,几乎所有用 Create 和 Copy 开头的函数,只要它们返回一个非托管的对象,我们几乎总是应该使用 takeRetainedValue() 方法来读取。

Unmanaged - NSHipster

参考

欢迎关注我的其它发布渠道