Swift与C语言交互编程!

916 阅读7分钟

作为一种可与 Objective-C 相互调用的语言,Swift 也具有一些与 C 语言的类型和特性,如果你的代码有需要,Swift 也提供了和常见的 C 代码结构混合编程的编程方式。

基本数据类型

Swift 提供了一些和 C 语言的基本类型如char,int,float,double等价的 Swift 基本数据类型。然而,这些 Swift 的核心基本类型之间并不能隐式的相互转换,如 Int。因此,只有你的代码明确要求它们时再使用这些类型,而 Int 可以在任何你想使用它的时候使用。

C 类型Swift 类型
boolCBool
char, signed charCChar
unsigned charCUnsignedChar
shortCShort
unsigned shortCUnsignedShort
intCInt
unsigned intCUnsignedInt
longCLong
unsigned longCUnsignedLong
long longCLongLong
unsigned long longCUnsignedLongLong
wchar_tCWideChar
char16_tCChar16
char32_tCChar32
floatCFloat
doubleCDouble

枚举

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发公众号:编程大鑫,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

Swift 引进了用宏NS_ENUM来标记的任何 C 风格的枚举类型。这意味着无论枚举值是在系统框架还是在自定义的代码中定义的,当他们导入到 Swift 时,他们的前缀名称将被截断。例如,看这个 Objective-C 枚举:

1.  //Objective-C
2.  typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
3.  UITableViewCellStyleDefault,
4.  UITableViewCellStyleValue1,
5.  UITableViewCellStyleValue2,
6.  UITableViewCellStyleSubtitle
7.  };

在 Swift 中这样来实现:

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

当您需要指向一个枚举值时,使用以点(.)开头的枚举名称:

1.  //Swift
2.  let cellStyle: UITableViewCellStyle = .Default 

Swift 也引进了标有NS_OPTIONS宏选项。而选项的行为类似于引进的枚举,选项还可以支持一些位操作,如 &,| 和 ~。在 Objective-C 中,你用一个空的选项设置标示恒为零(0)。在 Swift 中,使用 nil代表没有任何选项。

指针

Swift 尽可能避免让您直接访问指针。然而,当您需要直接操作内存的时候,Swift 也为您提供了多种指针类型。下面的表使用 Type 作为占位符类型名称来表示语法的映射。 对于参数,使用以下映射:

C 句法Swift 句法
const void *CConstVoidPointer
void *CMutableVoidPointer
const Type *CConstPointer
Type *CMutablePointer

对于返回类型,变量和参数类型的多层次指针,使用以下映射:

C 句法Swift 句法
void *COpaquePointer
Type *UnsafePointer

对于类(class)类型,使用以下映射:

C 句法Swift 句法
Type * const *CConstPointer
Type * __strong *CMutablePointer
Type **AutoreleasingUnsafePointer

C 可变指针

当一个函数被声明为接受CMutablePointer参数时,这个函数可以接受下列任何一个类型作为参数:

  • nil,作为空指针传入
  • 一个CMutablePointer类型的值
  • 一个操作数是 Type 类型的左值的输入输出表达式,作为这个左值的内存地址传入
  • 一个输入输出 Type[] 值,作为一个数组的起始指针传入,并且它的生命周期将在这个调用期间被延长

如果您像这样声明了一个函数:

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

那么您可以使用以下任何一种方式来调用这个函数:

1.  //Swift
2.  var x: Float = 0.0
3.  var p: CMutablePointer<Float> = nil
4.  var a: Float[] = [1.0, 2.0, 3.0]

6.  takesAMutablePointer(nil)
7.  takesAMutablePointer(p)
8.  takesAMutablePointer(&x)
9.  takesAMutablePointer(&a)

当函数被声明使用一个CMutableVoidPointer参数,那么这个函数接受任何和CMutablePointer相似类型的Type操作数。

如果您这样定义了一个函数:

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

那么您可以使用以下任何一种方式来调用这个函数:

//Swift
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参数时,这个函数可以接受下列任何一个类型作为参数:

  • nil,作为空指针传入
  • 一个CMutablePointer, CMutableVoidPointer, CConstPointer, CConstVoidPointer, 或者在必要情况下转换成CConstPointer的AutoreleasingUnsafePointer值
  • 一个操作数是 Type 类型的左值的输入输出表达式,作为这个左值的内存地址传入
  • 一个Type[]数组值,作为一个数组的起始指针传入,并且它的生命周期将在这个调用期间被延长

如果您这样定义了一个函数:

1.  //Swift
2.  func takesAConstPointer(x: CConstPointer<Float>) { /*...*/ } 

那么您可以使用以下任何一种方式来调用这个函数:

1.  //Swift
2.  var x: Float = 0.0
3.  var p: CConstPointer<Float> = nil

5.  takesAConstPointer(nil)
6.  takesAConstPointer(p)
7.  takesAConstPointer(&x)
8.  takesAConstPointer([1.0, 2.0, 3.0])

当函数被声明使用一个CConstVoidPointer参数,那么这个函数接受任何和CConstPointer 相似类型的Type操作数。  如果您这样定义了一个函数:

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

那么您可以使用以下任何一种方式来调用这个函数:

1.  //Swift
2.  var x: Float = 0.0, y: Int = 0
3.  var p: CConstPointer<Float> = nil, q: CConstPointer<Int> = nil

5.  takesAConstVoidPointer(nil)
6.  takesAConstVoidPointer(p)
7.  takesAConstVoidPointer(q)
8.  takesAConstVoidPointer(&x)
9.  takesAConstVoidPointer(&y)
10.  takesAConstVoidPointer([1.0, 2.0, 3.0])
11.  takesAConstVoidPointer([1, 2, 3])

自动释放不安全指针

当一个函数被声明为接受AutoreleasingUnsafePointer参数时,这个函数可以接受下列任何一个类型作为参数:

  • nil,作为空指针传入
  • 一个AutoreleasingUnsafePointer值
  • 其操作数是原始的,复制到一个临时的没有所有者的缓冲区的一个输入输出表达式,该缓冲区的地址传递给调用,并返回时,缓冲区中的值加载,保存,并重新分配到操作数。

注意:这个列表没有包含数组。

如果您这样定义了一个函数:

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

那么您可以使用以下任何一种方式来调用这个函数:

1.  //Swift
2.  var x: NSDate? = nil
3.  var p: AutoreleasingUnsafePointer<NSDate?> = nil
4.  
5.  takesAnAutoreleasingPointer(nil)
6.  takesAnAutoreleasingPointer(p)
7.  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 全局量,Swift 编译器会自动引进在 C 或 Objective-C 源文件中定义的简单宏。

复杂宏

在 C 和 Objective-C 中使用的复杂宏在 Swift 中并没有与之对应的定义。复杂宏是那些不用来定义常量的宏,而是用来定义包含小括号(),函数的宏。您在 C 和 Objective-C 使用复杂的宏是用来避免类型检查的限制和相同代码的重复劳动。然而,宏也会产生Bug和重构的困难。在 Swift 中你可以直接使用函数和泛型来达到同样的效果。因此,在 C 和 Objective-C 源文件中定义的复杂宏在 Swift 是不能使用的。

编译配置

Swift 代码和 Objective-C 代码以不同的方式进行条件编译。Swift 代码可以根据生成配置的评价配进行有条件的编译。生成配置包括 true 和 false 字面值,命令行标志,和下表中的平台测试函数。您可以使用-D <#Flag#>指定命令行标志。

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

注意:arch(arm) 的生成配置不会为64位 arm 设备返回true,当代码运行在为32位的 ios 模拟器器时,arch(i386) 的生成配置返回true。

一个简单的条件编译需要以下代码格式:

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

一个由零个或多个有效的 Swift 语句声明的statements,可以包括表达式,语句和控制流语句。您可以添加额外的构建配置要求,条件编译说明用 && 和 | | 操作符以及 ! 操作符,添加条件控制块用 #elseif:

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

与 C 语言编译器的条件编译相反,Swift 条件编译语句必须完全是自包含和语法有效的代码块。这是因为 Swift 代码即使没有被编译,也要全部进行语法检查。