猫·仁波切

会研发的PM才是好OP.

Swift Interop With C/ObjC Part 3 (Swift 与 ObjC 和 C 的交互,第三部分)

声明: 转载请注明,方便的情况下请知会本人. weibo

之前说那是最后一篇。可惜越来越发现有很多东西还没介绍到。事不过三。再坑一篇。

前言

本文解决如下问题

  • ObjC/C 中定义的某个类型、结构体,通过 Bridge Header 或者 Module 对应到 Swift 到底是什么类型
  • 指针间的转换问题

补充之前没解决的一些问题,比如提到 CMutablePointersizeof 是两个字长,那么在函数调用中是如何对应到 C 的指针的?

预备内容:

C/ObjC to Swift 对应规则

以下内容均适合 Objective-C 。第一部分适合 C 。

以下内容再 Xcode6-beta3 中不适用 请参考 Swift 在 Xcode6-beta3 中的变化

for C

可导出的类型定义

函数、枚举、结构体、常量定义、宏定义。

结构体定义支持:

1
2
3
typedef struct Name {...} Name;
typedef struct Name_t {...} Name;
struct Name { ... };

其中无法处理的结构体、函数类型、 varargs 定义不导出。预计以后版本会修复。带 bit field 的结构体也无法识别。

类型对应关系

仔细分析发现,诡异情况还很多。基础类型请参考上几篇。

在函数定义参数中:

类型 对应为
void * CMutableVoidPointer
Type *Type[] CMutablePointer<Type>
const char * CString
const Type * CConstPointer<Type>
const void * CConstVoidPointer

在函数返回、结构体字段中:

类型 对应为
const char * CString
Type *const Type * UnsafePointer<Type>
void *const void * COpaquePointer
无法识别的结构指针 COpaquePointer

另外还有如下情况:

全局变量、全局常量(const)、宏定义常量(#define) 均使用 var,常量不带 set

结构体中的数组,对应为元祖,例如 int data[2] 对应为 (CInt, CInt),所以也许。。会很长。数组有多少元素就是几元祖。

for ObjC

ObjC 明显情况要好的多,官方文档也很详细。

除了 NSError ** 转为 NSErrorPointer 外,需要注意的就是:

函数参数、返回中的 NSString * 被替换为 String!NSArray * 被替换为 AnyObject[]!

而全局变量、常量的 NSString * 不变。

关于 CMutablePointer 的行为

上回说到 CMutablePointerCConstPointerCMutableVoidPointerCConstVoidPointer 四个指针类型的字长是 2,也就是说,不可以直接对应为 C 中的指针。但是前面说类型对应关系的时候, C 函数声明转为 Swift 时候又用到了这些类型,所以看起来自相矛盾。仔细分析了 lldb 反汇编代码后发现,有如下隐藏行为:

in Swift

在纯 Swift 环境下,函数定义等等、这些类型字长都为 2,不会有任何意外情况出现。

in C/ObjC

当一个函数的声明是由 Bridge Header 或者 LLVM Module 隐式转换而来,且用到了这四个指针类型,那么代码编译过程中类型转换规则、隐式转换调用等规则依然有效。只不过在代码最生成一步,会插入以下私有函数调用之一:

1
2
3
4
@transparent func _convertCMutablePointerToUnsafePointer<T>(p: CMutablePointer<T>) -> UnsafePointer<T>
@transparent func _convertCConstPointerToUnsafePointer<T>(p: CConstPointer<T>) -> UnsafePointer<T>
@transparent func _convertCMutableVoidPointerToCOpaquePointer(p: CMutableVoidPointer) -> COpaquePointer
@transparent func _convertCConstVoidPointerToCOpaquePointer(p: CConstVoidPointer) -> COpaquePointer

这个过程是背后隐藏的。然后将转换的结果传参给对应的 C/ObjC 函数。实现了指针类型字长正确、一致。

结论

作为程序员,需要保证调用 C 函数的时候类型一致。如果有特殊需求重新声明了对应的 C 函数,那么以上规则不起作用,所以重声明 C 中的函数时表示指针不可以使用这四个指针类型。

再说指针

虚线表示直接隐式类型转换。其中 UnsafePointer<T> 可以通过用其他任何指针调用构造函数获得。

CMutablePointer<T>CMutableVoidPointer 也可以通过 Array<T> 的引用隐式类型转换获得 ( &arr )。

椭圆表示类型 sizeof 为字长,可以用于声明 C 函数。

四大指针可以用 withUnsafePointer 操作。转换为 UnsafePointer<T>。上一节提到的私有转换函数请不要使用。

字符串

之前的文章已经介绍过怎么从 CString 获取 String (静态方法 String.fromCString)。

String 获取 CString 也说过, 是用 withCString

也可以从 CString(UnsafePointer.alloc(100)) 来分配空数组。

参考

Comments