使用Swift与Cocoa和Objective-C交互-(6)与 C API 交互



- 作者: SwiftCafe


与 C API 交互

作为与 Objective-C 互操作性的一部分,Swift 维护了与一定数量的 C 语言类型和特性的兼容性。Swift 还提供了与 C 构造器和设计模式交互的方式。

原始类型

Swift 提供了和 C 原生整型的等值-例如,charintfloatdouble。然而,没有在这些类型和 Swift 整型类型之间的隐式转换,比如 Int。因此,如果你的代码实在需要它们,才使用这些类型,否则在任何可能的情况下,都使用 Int

类型 Swift 类型
bool CBool
char, signed char CChar
unsigned char CUnsignedChar
short CShort
unsigned short CUnsignedShort
int CInt
unsigned int CUnsignedInt
long CLong
unsigned long CUnsignedLong
long long CLongLong
unsigned long long CUnsignedLongLong
wchart CWideChar
char16t CChar16
char32t CChar32
float CFloat
double CDouble

枚举

Swift 会导入所有用 NS_ENUM 宏来标记的 C 风格的枚举作为 Swift 枚举。这意味着,枚举值名称的前缀,在导入到 Swift 中得时候,会被截断,无论他们是定义在系统框架中,或是自定义代码中。例如,看一下这个 Objective-C 枚举:

1
2
3
4
5
6
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};

在 Swift 中,它被导入为这样的值:

1
2
3
4
5
6
enum UITableViewCellStyle: Int {
case Default
case Value1
case Value2
case Subtitle
}

当你引用枚举值,使用点号(.)后面加上枚举值名称的形式。

1
let cellStyle: UITableViewCellStyle = .Default

Swift 还会导入用 NS_OPTIONS 宏来标记的选项。选项的行为类似于导入的枚举,选项还可以支持一些位运算,比如 &,|,和 ~。在 Objective-C 中,你可以用 0 来代表空的选项集。在 Swift 中使用 nil 来表示缺少的选项。

指针

Swift 尽量避免让你直接访问到指针。然而,当你需要直接访问内存的时候,也有几个不同种类的指针供你使用。下面的表格,使用 Type 来作为类型名称的占位符,用于指示语法的映射。

对于参数,下面的映射适用:

C 语法 Swift 语法
const void * CConstVoidPointer
void * CMutableVoidPointer
const Type * CConstPointer<Type>
Type * CMutablePointer<Type>

对于多于一级指针深度的返回类型,变量和参数类型,适用于如下的映射:

C 语法 Swift 语法
void * COpaquePointer
Type * UnsafePointer<Type>

对于 class 类型,适用下面的映射:

C 语法 Swift 语法
Type * const * UnsafePointer<Type>
Type * __strong * UnsafeMutablePointer <Type>
Type ** AutoreleasingUnsafePointer <Type>

C Mutable Pointers

当函数定义为接受 CMutablePointer<Type> 类型的参数时,他可以接受一下参数的任意一种:

  • nil,代表空指针。
  • CMutablePointer
  • 输入-输出表达式(可被保存修改的参数),操作数被存储在 Type 类型的左值中,并使用左值的地址传入。
  • 输入-输出 Type 类型的值,作为指向数组首元素的指针传入,并在调用的时间中是延长生存周期的。

如果你将函数定义为下面这样:

1
func takesAMutablePointer(x: CMutablePointer<Float>) { /*...*/ }

你可以以下面任意的方式调用它:

1
2
3
4
5
6
7
var x: Float = 0.0
var p: CMutablePointer<Float> = nil
var a: [Float] = [1.0, 2.0, 3.0]
takesAMutablePointer(nil)
takesAMutablePointer(p)
takesAMutablePointer(&x)
takesAMutablePointer(&a)

当一个函数被定义为接受 CMutableVoidPointer 类型的参数,他也可以接受 CMutablePointer%lt;Type%gt; 形式定义的任意 Type 类型的参数。

如果你这样定义一个函数:

1
func takesAMutableVoidPointer(x: CMutableVoidPointer) { /* ... */ }

你可以用以下任意方式调用它:

1
2
3
4
5
6
7
8
9
10
var x: Float = 0.0, y: Int = 0
var p: CMutablePointer<Float> = nil, q: CMutablePointer<Int> = nil
var a: [Float] = [1.0, 2.0, 3.0], b: Int = [1, 2, 3]
takesAMutableVoidPointer(nil)
takesAMutableVoidPointer(p)
takesAMutableVoidPointer(q)
takesAMutableVoidPointer(&x)
takesAMutableVoidPointer(&y)
takesAMutableVoidPointer(&a)
takesAMutableVoidPointer(&b)

C 常量指针

当一个函数定义为使用 CConstPointer%lt;Type%gt; 类型的参数,他可以接受如下形式的传入:

  • nil,作为空指针传入
    *CMutablePointer<Type>, CMutablePointer<Type>, CMutablePointer<Type>, CMutablePointer<Type>, 或 CMutablePointer<Type> 的值,在需要的时候会被转换成 CConstPointer<Type>
  • 左值为 Type 类型的输入-输出表达式,用左值的地址作为参数传递。
  • 一个 [Type] 值,指向数组首元素的指针传递进来,并在调用过程中声明周期进行扩展。

如果你像这样定义了函数:

1
func takesAConstPointer(x: CConstPointer%lt;Float%gt;) { /*...*/ }

你可以用以下任意方式调用它:

1
2
3
4
5
6
var x: Float = 0.0
var p: CConstPointer%lt;Float%gt; = nil
takesAConstPointer(nil)
takesAConstPointer(p)
takesAConstPointer(&x)
takesAConstPointer([1.0, 2.0, 3.0])

当函数定义为接受 CConstVoidPointer 类型的参数时,它可以接受用 CConstPointer%lt;Type%gt; 形式定义的任意 Type 类型。

如果你像这样定义了函数:

1
func takesAConstVoidPointer(x: CConstVoidPointer) { /* ... */ }

你可以以下面任意形式调用它:

1
2
3
4
5
6
7
8
9
var x: Float = 0.0, y: Int = 0
var p: CConstPointer<Float> = nil, q: CConstPointer<Int> = nil
takesAConstVoidPointer(nil)
takesAConstVoidPointer(p)
takesAConstVoidPointer(q)
takesAConstVoidPointer(&x)
takesAConstVoidPointer(&y)
takesAConstVoidPointer([1.0, 2.0, 3.0])
takesAConstVoidPointer([1, 2, 3])

AutoreleasingUnsafePointer

当一个函数定义为接受 AutoreleasingUnsafePointer<Type> 类型的参数,他接受下面任意一种:

  • nil 作为空指针传入。
  • AutoreleasingUnsafePointer%lt;Type%gt; 类型的值。
  • 输入-输出表达式,操作符是原始类型的拷贝。这个缓冲区的地址被传入被调者,并且在返回的时候,这个缓冲区中得值会被调用,retain,并且重新赋值到操作值中。

注意这个列表中不包含数组。

如果你像这样定义函数:

1
func takesAnAutoreleasingPointer(x: AutoreleasingUnsafePointer<NSDate?>) { /* ... */ }

你可以用如下的方式调用它:

1
2
3
4
5
var x: NSDate? = nil
var p: AutoreleasingUnsafePointer<NSDate?> = nil
takesAnAutoreleasingPointer(nil)
takesAnAutoreleasingPointer(p)
takesAnAutoreleasingPointer(&x)

注意 C 函数指针没有引入到 Swift 中。

全局常量

在 C 和 Objective-C 源文件中定义的全局常量会被 Swift 编译器自动导入为 Swift 全局常量。

预处理指令

Swift 编译器中不包含预处理器,而是利用编译时属性,构建参数和语言特性来完成同样的功能。因此,预处理指令没有被导入到 Swift 中。

简单宏

在 C 和 Objective-C 中,你使用 #define 指令来定义原始常量,而在 Swift 中,你使用全局常量来代替。例如,常量定义 #define FADE_ANIMATION_DURATION 0.35 可以在 Swift 中用 let FADE_ANIMATION_DURATION = 0.35 来更好的表示。因为这样类似常量的简单宏定义会直接映射为 Swift 全局常量,编译器会自动导入定义在 C 或 Objective-C 源文件中的简单宏。

复合宏

在C 和 Objective-C 中使用的复合宏没有 Swift 中与之对应的东西。复合宏是那些不定义常量,而是用括号进行类似函数功能的宏。你在 C 和 Objective-C 中使用复合宏定义来避免类型检测的限制,或者避免重复输入大量的模板代码。然而,宏让调试和重构变得困难。在 Swift 中,你可以使用函数和泛型来取得同样的结果,而不需要进行任何的折中操作。这样,在 C 和 Objective-C 源文件中的复合宏,在你的 Swift 代码中是不可用的。

构建配置

Swift 和 Objective-C 代码会条件性的用不同的方式编译。Swift 代码会条件性地根据 构建配置 的结果值进行配置。构建配置包括字面值truefalse,命令行标记,还有下面表格列出的平台测试函数。你可以指定命令行参数 -D <#flag#>

函数 有效参数
os() OSX,iOS
arch() x86_64,arm,arm64,i386

注意
arch(arm) 对于 ARM 64 位的设备不会返回 true。 arch(i386) 当代码作为 32位 >iOS 模拟器编译的时候,会返回 true。

简单的条件编译语句使用如下形式:

1
2
3
4
5
#if build configuration
statements
#else
statements
#endif

statements 又零个或多个有效的 Swift 语句组成,可以包括表达式,语句,和控制流语句。你可以为条件编译语句添加 &and;&and;|| 操作符来增加附加的编译选项,使用 ! 操作符来取反,并且通过 #elseif 来添加条件语句块。

1
2
3
4
5
6
7
#if build configuration && !build configuration
statements
#elseif build configuration
statements
#else
statements
#endif

和 C 预处理器中得条件编译语句对比,Swift 中的条件编译语句必须是用字包含并且语义正确的代码块进行包围。这是因为所有的 Swift 代码都会被进行语法检查,即使他们是没有被编译的。

如果你觉得这篇文章有帮助,还可以关注微信公众号 swift-cafe,会有更多我的原创内容分享给你~

本站文章均为原创内容,如需转载请注明出处,谢谢。




微信公众平台
更多精彩内容,请关注微信公众号


公众号:swift-cafe
邮件订阅
请输入您的邮箱,我们会把最新的内容推送给您: