内容简介:Swift 与 C 的交互
随着 Swift 开源的即将到来,我们很快就可以在没有 Apple 框架的平台上运行 Swift 代码了。因此,我们怎样才能使用诸如快速 排序 数组之类的功能呢?在本次 #Pragma 2015 演讲中,Chris Eidhof 阐述了如何使用 Swift 来封装一个 C 类库(:grey_exclamation:),从而让 Swift 开发者能够在所有平台上使用 C 标准库。
我们将会看下如何使用一些简洁的办法来处理 C 与 Swift 之间的交互。具体而言,就是如何在 Swift 中使用 C 的 API。我们以快速排序这个例子开始。
我们现在准备使用 C 标准库中的快速排序方法。通常而言,这个办法是非常不明智的,因为 Swift 中已经内置了排序函数了,由于这些内置的排序函数经过了优化并且工作性能良好,因此一般情况下我们应该使用这些函数。我们这不过是举一个如何使用 C API 的例子,千万不要使用这种版本的快速排序!现在让我们找一个数字数组,然后对其进行排序。在 Swift 中,通常情况下你会这么做:
let numbers = [3,1,2,17,4] print(numbers.sort())
你也可以使用可以修改数组本身的 sortInPlace()
。
var numbers = [3,1,2,17,4] numbers.sortInPlace() print(numbers)
好的,现在我们就要使用 C 函数让这个操作变得更复杂一点。我们将要使用 qsort
。在终端中输入 man qsort
指令,你会看到:
void qsort(void *base, size_t nel, size_t width, int (*compar)(const void *, const void *));
void
是 qsort
函数的返回类型。它不返回任何东西。这个快排函数同时还修改了原有的数组。 qsort
函数的第一个参数是一个指向数组首地址的 void
指针。这是 C 用来表达:“这是一个数组,其中可能存在任何东西”的方式。对于一个整数数组来说,指向首地址的应该是一个 int
指针,但是这里由于我们不知道数组中会是什么类型,因此这里的参数是 void
指针。第二个参数是 nel
,它指的是数组中元素的数目。C 中的数组必须有一个指针指向其首元素,并且还要指定元素的数目。第三个参数是 width
,它是数组中单个元素所占用的内存空间大小。如果要执行遍历操作的话,那么就需要通过这个参数来实现递增。最后一个参数是一个比较函数,它接收两个 const void
类型的指针,然后返回一个整数。这两个指针表示数组中进行比较的两个元素指针。元素通过 const void
指针进行传递,这意味着我们也可以将任何类型的东西传递进去。 const
标识意味着你不能改变元素的值,它是一个 常量
。星号(*)表明这是一个 C 函数指针,你便可以在 Swift 中使用这些函数指针。
Get more development news like this
var numbers = [2, 1, 17, 3] qsort(&numbers, numbers.count, sizeOf(Int)) { (l, r) -> Int32 in let left: Int = UnsafePointer(l).memory let right: Int = UnsafePointer(r).memory if left < right { return -1 } if left == right {return 0 } return 1 }
这个函数的第一个参数需要为指向数组第一个元素的指针。在 Swift 当中,我们很容易实现这个功能,那就是 &numbers
。第二个参数是数组中元素的总数。我们可以使用 numbers.count
。接下来,我们需要得到单个元素的空间大小。如果你用过 C 的话,你肯定知道 sizeof()
这个函数。但是这个函数并不适合,我们过一会儿会对此进行说明。第四个参数是用来比较两个数字的一个比较函数。这个闭包获取两个参数,也就是比较符左边和右边的两个元素。它需要返回一个 Int32 类型的数字。然后我们对着两个元素进行比较。这样我们便能够使用 C 中内置的快排方法得到一个排序好的数组了。
我们可以用一个方法将其好好封装起来,这样我们就可以用简单的方法时不时使用这个函数了。
func quicksort(var input: [Int]) -> [Int] { qsort(&input, input.count, sizeOf(Int)) { (l, r) -> Int32 in let left: Int = UnsafePointer(l).memory let right: Int = UnsafePointer(r).memory if left < right { return -1 } if left == right {return 0 } return 1 } return input }
那么对于字符串来说起作用吗?最简单的方式就是将上面那个函数进行拷贝,然后改点东西就成:
func quicksort(var input: [String]) -> [String] { qsort(&input, input.count, sizeOf(String)) { (l, r) -> Int32 in let left: String = UnsafePointer(l).memory let right: String = UnsafePointer(r).memory if left < right { return -1 } if left == right {return 0 } return 1 } return input }
如果我们想要为其他类型创建快速排序的话,我们需要一遍又一遍地复制,这是一项很繁杂的工作。我们可以通过 Swift 的泛型让事情变得简单一些。我们使用快排的第二种版本: qsort_b
。
void qsort_b(void *base, size_t nel, size_t width, int (^compar)(const void *, const void *));
我们对这个类型进行分析。 qsort_b
接收一个 void
指针。这个指针指向数组中的第一个元素。然后接收元素总数、元素占用空间,以及比较函数指针作为参数。然而,如果你仔细观察的话你会发现这个函数有一个很不起眼的不同点。在 qsort
中我们使用的是星号。在 qsort_b
中,我们用的是 Caret(^) 符号。这意味着这里接收的是一个闭包。如果你在 Objective-C 、C 和 Swift 中都可以使用闭包的话,那么就可以在闭包外指定泛型了。使用 qsort_b
的话就可以使用下面这段代码了:
func quicksort<A: Comparable>(var input: [A]) -> [A] { qsort_b(&input, input.count, sizeOf(A)) { (l, r) -> Int32 in let left: A = UnsafePointer(l).memory let right: A = UnsafePointer(r).memory if left < right { return -1 } if left == right {return 0 } return 1 } return input }
现在,我们拥有了一个泛型的快排函数。只要 A
类型是遵循 Comparable 协议的,那么这个函数就可以用。我们当然可以说“万事大吉”。不过,我们需要让事情变得更复杂些。快排拥有一个基于闭包的变体,然而绝大多数你所使用的 C API 都不会有类似的变体。它们只能够接受函数指针。在 C 中,惯用做法是使用传递上下文的函数指针。
如果你在封装 C 标准库中的其他函数的话,一般来说你都不能使用基于闭包的 API 变体。你需要使用第三种方式来解决这个问题,也就是快排的第三种形式:
void qsort_r(void *base, size_t nel, size_t width, void *thunk, int (*compar)(void *, const void *, const void *));
这个变体参数有相同的数组指针、有元素总数、有元素内存大小,不同的是多了一个名为 thunk
的空指针。比较函数同样发生了变化。 compar
多接收一个空指针作为参数,另外两个才是空指针常量。 thunk
这个新增的参数作为指向 compar
函数的第一个参数传入。我们可以在这个 thunk
指针中放入任何东西,然后我们在比较函数内部再获取这个参数。这也是绝大多数 C API 都提供的一种方式。因此,与 qsort_b
相比,我更喜欢使用 qsort_r
,然后将比较函数作为参数传递到 thunk
当中。
extension Comparable { static func compare(l: UnsafePointer<Void>, _ r: UnsafePointer<Void>) -> Int32 { let left: Self = UnsafePointer(l).memory let right: Self = UnsafePointer(r).memory if left < right { return -1 } if left == right { return 0 } return 1 } } typealias CompareFunc = (UnsafePointer<Void>, UnsafePointer<Void>) -> Int32 func cmp(thunk: UnsafeMutablePointer<Void>, l: UnsafePointer<Void>, r: UnsafePointer<Void>) -> Int32 { let compareFunc: CompareFunc = UnsafeMutablePointer(thunk).memory return compareFunc(l,r) } extension Array where Element: Comparable { func quicksort() -> [Element] { var array = self var compare = Element.compare qsort_r(&array, array.count, strideof(Element), &compare, cmp) return array } mutating func quicksortInline() { self = quicksort() } } var myArray = ["Hello", "ABC", "abc", "test"] myArray.quicksortInline() print(myArray)
我们都干了些啥?我们简单回顾一下。首先我们以一个非常简单的 qsort
例子开始,对一些数字进行了排序。接着,我们将其放入到函数中以便让其能够对任何数字类型的数组有效。然后我们开始为其增加泛型特性,功能变得更加复杂了。闭包相同来说还好,但是一旦我们需要使用 qsort_r 的时候,事情变得更加离奇了。然而,这在 C 的函数库中是一个很常见的模式,当你想封装 C 函数库的时候,你都可以使用这个方法。你或许会想“我为啥要封装 C 的函数库呢?”我认为一旦 Swift 开源之后,我们就会让其在诸如 Linux 等多个平台上能够很好的运行。而在 Linux 上我们很可能无法访问存在于 Cocoa 和 iOS 中的所有 Apple 框架。我们需要封装关于网络、绘图等一系列的框架。这样,知道关于如何与 C 函数库协同工作就变得很重要了。这就是为什么我认为这个知识是很有用的。
问:如果你打算在项目中使用 C 的函数和类库的话,那么把文件拷贝到项目当中就可以在相同的模组当中用 Swfit 调用了吗?
Chirs:没错,这和在 Swift 当中使用 Objective-C 很相似。
问:假设你有一个 C 类库,并且你能使用的 API 数据都只能以函数指针的形式来显示,不能够使用任何的闭包 API 或者转换 API的话。是否就没有别的办法了吗,或者只能够使用上下文来进行传递了呢?
Chris:是的,如果没有 thunk 或者经常调用上下文的话,那么我们就别无他法了。
问:你开始这么做的动机是什么呢?当你第一次开始这么做的时候你的感受如何?当最后成功的时候感受又是如何呢?
Chris:我们先谈谈所谓的“动机”。正常情况下,我不会使用 C 类库,但是当苹果开源了 Swift 之后,我就在想这么做会不会很有意思。与此同时,我和 Airspeed Velocity 组建了一个撰写 Swift 新书籍的团队。这个计划是在苹果宣布开源之前实施的,之后,当苹果宣布开源的时候我就意识到:“我的天,Swift 即将无处不在了!”。对于绝大多数平台来说,我们无法使用苹果的自建框架。我很享受在 Linux 编写 Swift 代码的感觉,以及使用 Swift 来编写后端程序。然而,我就需要学习如何封装 C API。因此,在我们的书当中,我们决定着重强调与 C 语言交互的这部分。
如果你使用的 C API 是异步工作的话,你或许会考虑很多关于内存管理的工作。你需要保留某些对象,并且确保在离开函数范围后不会出现内存溢出的情况。不过当你读完 Kernighan 和 Ritchie 那本 C 语言书之后,你会发现这十分简单。不过,对于 C API 和库来说,它们之间还有大量的不同。这可能会造成极大的困扰。接着,我意识到只需要掌握很少一部分的技巧,就可以将这些 API 进行封装,然后就可以在 Swift 当中使用它们了,我觉得这个功能十分强大。现在我知道我可以在 Swift 当中使用绝大多数的 C 类库了。我为此建立封装,进行充分的测试,最后就可以为我所用了。
问:在 GitHub 上有一个名为 SwiftGo 的项目展示了 Go 和 Swift 的桥接工作,这个项目看起来很有前景。你对在 Swift 与诸如 Go 这样的语言建立桥接的工作是怎么看待的呢?
Chris:我不是很确切地知道 SwiftGo 所做的工作,不过如果我的记忆没错的话,他们是为 Go 功能封装了一个库,而不是为 Go 语言本身封装了一个库。我觉得这个项目很强大因为现在你就可以很好地使用它了。我很乐意见到越来越多的人在 Swift 当中封装 C 类库。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- iOS 12 人机交互指南:交互(User Interaction)
- 生活NLP云服务“玩秘”站稳人机交互2.0语音交互场景
- asyncio之子进程交互
- 以太坊交互工具
- 学习 PixiJS — 交互工具
- Python基础(7)-用户交互
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
算法技术手册(原书第2版)
George T. Heineman、Gary Pollice、Stanley Selkow / 杨晨、曹如进 / 机械工业出版社 / 2017-8-1 / 89.00元
本书使用实际代码而非伪代码来描述算法,并以经验主导支撑数学分析,侧重于应用且规范严谨。本书提供了用多种程序设计语言实现的文档化的实际代码解决方案,还介绍了近40种核心算法,其中包括用于计算点集的Voronoi图的Fortune算法、归并排序、多线程快速排序、AVL平衡二叉树实现以及空间算法。一起来看看 《算法技术手册(原书第2版)》 这本书的介绍吧!