在Swift中使用遗留的C API
Swift的类型系统的设计目的在于简化我们的生活,为此它强制用户遵守严格的代码规范来达到这一点。毫无疑问这是一件大好事,它鼓励程序员们编写 更好更正确的代码。然而,当Swift与历史遗留的代码库、特别是C语言库进行交互时,问题出现了。我们需要面对的现实是许多C语言库滥用类型,以至于它 们对Swift的编译器并不友好。苹果的Swift团队的确花了不少功夫来支持C的一些基础特性,比如C字符串。但当在Swift中使用历史遗留的C语言 库时,我们还是会面临一些问题。下面我们就来解决这些问题。
在开始之前我必须先提醒一下,这篇文章代码里的许多操作有潜在的安全问题,即使 它们绕过了Swift编译器的类型系统的检查,我建议你仔细的阅读并且不要复制粘贴文章内的代码。它们不是Stack Overflow,不恰当的使用它们会真的导致记忆体损坏、内存泄露,或者至少让你的程序崩溃。
基础概念
大多数时候,C语言指针有两种方法导入到Swift中:
1
|
UnsafePointerUnsafeMutablePointer |
这里的T是C类型的等价的Swift类型。声明为常量的指针被导入为UnsafePointer,非常量的指针则被导入为UnsafeMutablePoinger。
这里有一些示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
C: void myFunction(const int *myConstIntPointer); Swift: func myFunction(myConstIntPointer: UnsafePointer) C: void myOtherFunction(unsigned int *myUnsignedIntPointer); Swift: func myOtherFunction(myUnsignedIntPointer: UnsafeMutablePointer) C: void iTakeAVoidPointer(void *aVoidPointer); Swift: func iTakeAVoidPointer(aVoidPointer: UnsafeMutablePointer) |
如果不知道指针的类型,比如一个为前置声明的指针,则使用COpaquePointer。
1
2
3
4
5
6
|
C: struct SomeThing; void iTakeAnOpaquePointer(struct SomeThing *someThing); Swift: func iTakeAnOpaquePointer(someThing: COpaquePointer) |
传递指针到Swift对象
在很多情况下,传递指针到Swift对象和使用inout运算符一样简单,后者类似于C语言中的and运算符。
1
2
3
4
5
6
|
Swift: let myInt: = 42 myFunction(&myInt) var myUnsignedInt: UInt = 7 myOtherFunction(&myUnsignedInt) |
这里有两个非常重要但容易被忽视的细节。
1. 当使用inout运算符时,使用var声明的变量和使用let声明的常量被分别转换到UnsafePointer和 UnsafeMutablePoinger,如果你不注意原来代码中的类型,就很容易出错。你可以试着向本来是UnsafeMutablePoinger 的地方传递一个UnsafePointer看看,编译器会报错。
2. 这个运算符只在将Swift值和引用作为函数参数传递的上下文时生效,且该函数参数只接受UnsafePointer和UnsafeMutablePoinger两种类型。你不能在其它上下文获得这些指针。比如,下面的代码是无效的,并且会返回编译错误。
1
2
3
|
Swift: let x = 42 let y = &x |
你可能会不时的需要交互操作一个API。来获取或返回一个空指针以代替显式类型,不幸的是这种做法在C语言里很普遍,导致无法指定一个通用类型。
1
2
|
C: void takesAnObject(void *theObject); |
如果你确定函数需要获取什么类型的参数,你可以使用withUnsafePointer和unsafeBitCast将对象强制转换为空指针。比如,假设takesAnObject需要获取指向int的指针。
1
2
3
4
5
|
var test = 42 withUnsafePointer(&test, { (ptr: UnsafePointer) -> Void in var voidPtr: UnsafePointer = unsafeBitCast(ptr, UnsafePointer.self) takesAnObject(voidPtr) }) |
为了转换它,首先我们需要调用withUnsafeMutablePointer,这个通用函数包含两个参数。
第 一个参数是T类型的inout运算符,第二个是(UnsafePointer) -> ResultType的闭包。函数通过指向第一个参数的指针来调用闭包,然后然后将其作为闭包唯一的参数传递,最后函数返回闭包的结果。在上面的例子里, 闭包的类型被设置为Void,因此将不返回值。返回值的例子如下:
1
2
3
4
5
6
|
let ret = withUnsafePointer(&test, { (ptr: UnsafePointer) -> Int32 in var voidPtr: UnsafePointer = unsafeBitCast(ptr, UnsafePointer.self) return takesAnObjectAndReturnsAnInt(voidPtr) }) println(ret) |
注意:你需要自己修改指针,通过withUnsafeMutablePointer变体来完成修改。
为方便起见,Swift也包括传递两个指针的变体:
1
2
3
4
5
6
7
8
9
10
|
var x: Int = 7 var y: Double = 4 withUnsafePointers(&x, &y, { (ptr1: UnsafePointer, ptr2: UnsafePointer) -> Void in var voidPtr1: UnsafePointer = unsafeBitCast(ptr1, UnsafePointer.self) var voidPtr2: UnsafePointer = unsafeBitCast(ptr2, UnsafePointer.self) takesTwoPointers(voidPtr1, voidPtr2) }) |
关于unsafeBitCast
unsafeBitCast 是一个极度危险的操作。文档将其描述为“将某物强制转换为和其他东西相同的比特位”。在上面我们能够安全的使用它的原因是,我们只是简单的转换不同类型的 指针,并且这些指针的比特位都是相同的。这也是我们为什么必须先调用withUnsafePointer来获取UnsafePointer,然后将其转换 为UnsafePointer的原因。
在一开始这可能会造成迷惑,特别是当处理与指针相同的类型时,比如Swift里的Int(在目前所有可用的平台,一个指针的长度是一个字符,Int的长度同样也是一个字符)。
比如你很容易就会犯下面的错误:
1
2
|
var x: Int = 7 let xPtr = unsafeBitCast(x, UnsafePointer.self) |
这段代码的意图是获取一个指针并传递给x。它会给人造成误解,尽管编译能通过并且能运行,但会导致一个意外错误。这是因为C API没有获取指针并传递给x,而是接收位于0x7或者其他地方的指针。
因为unsafeBitCast要求类型的长度相等,所以当试图转换Int8或者一个字节的整型时没那么阴险了。
1
2
|
var x: Int8 = 7 let xPtr = unsafeBitCast(x, UnsafePointer.self) |
这段代码会简单的导致unsafeBitCast抛出异常和程序崩溃。
与C语言中的结构体交互
让 我们用实际的示例来展示这一部分。如果你想检索计算机所运行的系统信息,有一个C API:uname(2)可以达到目的。它接收指针指到一个数据结构,并且用系统信息填充所提供的对象,如OS名称和版本或者硬件识别符。但这里有一个问 题,导入到Swift的结构体是这样:
1
2
3
4
5
6
7
8
|
struct utsname { var sysname: (Int8, Int8, ...253 times..., Int8) var nodename: (Int8, Int8, ...253 times..., Int8) var release: (Int8, Int8, ...253 times..., Int8) var version: (Int8, Int8, ...253 times..., Int8) var machine: (Int8, Int8, ...253 times..., Int8) } |
Swift将C中的数组字面量作为元组导入,并且默认的初始化程序要求每个字段都有值,所以如果你用Swift通常的做法来做的话,它将会变成:
1
2
3
4
5
|
var name = utsname(sysname: (0, 0, 0, ..., 0), nodename: (0, 0, 0, ..., 0), etc) utsname(&name) var machine = name.machine println(machine) |
这不是一个好方法。并且还存在另一个问题。因为utsname里的machine字段是元组,所以当使用println时将输出256位的Int8,但实际上只有字符串开始的几个ASCII值是我们需要的。
那么,如何解决这个问题?
Swift里的UnsafeMutablePointer提供两个方法,alloc(Int)和dealloc(Int),分别用来分配和解除分配模板T的数量参数。我们可以用这些API来简化我们的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
let name = UnsafeMutablePointer.alloc(1) uname(name) let machine = withUnsafePointer(&name.memory.machine, { (ptr) -> String? in let int8Ptr = unsafeBitCast(ptr, UnsafePointer.self) return String.fromCString(int8Ptr) }) name.dealloc(1) if let m = machine { println(m) } |
第一步是调用withUnsafePointer,将机器的元组传递给它,并通知它我们的闭包将返回一个附加字符串。
在 闭包里面我们将指针转换为UnsafePointer,即该值的最等价的表述。除此之外,Swift的String包含一个类方法来初始化 UnsafePointer,这里的CChar是Int8类型的别名(typealias),所以我们能够将我们的新指针传递给初始化程序并且返回所需要 的信息。
在获取withUnsafePointer的结果之后,我们能够测试它是否是let的条件声明,并打印出结果。对这个例子来说,它输出了期望的字段“x86_64”。
总结
最 后,说一下免责声明。在Swift中使用不安全的API应该被视为最后手段,因为它们是潜在不安全的。当我们转换遗留的C和Objective-C代码到 Swift中,有很大可能性我们会继续需要这些API来兼容现有工具。然而,当使用withUnsafePointer和unsafeBitCast作为 首选手段时应该始终抱有怀疑态度,并寻找其他更好的解决方案。
新的代码应该尽可能符合语言习惯,不要在Swift代码中使用不安全的API。作为软件开发者,你应该了解如何使用你的工具以及什么地方该使用和什么地方不该使用。Swift将现代化带给了OS X和iOS开发,我们必须尊重它的理念。
在Swift中使用遗留的C API的更多相关文章
- 总结 Swift 中随机数的使用
在我们开发的过程中,时不时地需要产生一些随机数.这里我们总结一下Swift中常用的一些随机数生成函数.这里我们将在Playground中来做些示例演示. 整型随机数 如果我们想要一个整型的随机数,则可 ...
- [翻译]理解Swift中的Optional
原文出处:Understanding Optionals in Swift 苹果新的Swift编程语言带来了一些新的技巧,能使软件开发比以往更方便.更安全.然而,一个很有力的特性Optional,在你 ...
- swift中第三方网络请求库Alamofire的安装与使用
swift中第三方网络请求库Alamofire的安装与使用 Alamofire是swift中一个比较流行的网络请求库:https://github.com/Alamofire/Alamofire.下面 ...
- Swift 中的指针使用
SWIFT 中 指针被映射为泛型 UnsafePointer<T> UnsafeMutablePointer<T> 表示一组连续数据指针的 UnsafeBufferPoint ...
- 在Swift中应用Grand Central Dispatch(下)
在第一部分中, 你学到了并发,线程以及GCD的工作原理.通过使用dispatch_barrrier和dispatch_sync,你做到了让 PhotoManager单例在读写照片时是线程安全的.除此之 ...
- 在Swift中应用Grand Central Dispatch(上)转载自的goldenfiredo001的博客
尽管Grand Central Dispatch(GCD)已经存在一段时间了,但并非每个人都知道怎么使用它.这是情有可原的,因为并发很棘手,而且GCD本身基于C的API在 Swift世界中很刺眼. 在 ...
- swift中文文档- 类型转换
未翻译完 待续(英语烂,求斧正) Type Casting 类型转换 Type casting is a way to check the type of an instance, and/or to ...
- Swift中的HTTP请求
iOS开发中大部分App的网络数据交换是基于HTTP协议的.本文将简单介绍在Swift中使用HTTP进行网络请求的几种方法. 注意:网络请求完成后会获得一个NSData类型的返回数据,如果数据格式为J ...
- 思考 Swift 中的 MirrorType 协议
Swift中的反射非常有限,仅允许以只读方式访问元数据的类型子集.或许 Swift 因有严格的类型检验而不需要反射.编译时已知各种类型,便不再需要进行进一步检查或区分.然后大量的 Cocoa API ...
随机推荐
- 针对各主流数据mysql、sqlserver、oracle中文乱码问题。
针对各主流数据mysql.sqlserver.oracle当以编码格式gbk存放数据时,要注意字符串类型的字段,要采用宽字符串nvarchar存放,前提是当你的应用程序是utf8编码,而数据库是gbk ...
- [RxJS] Reactive Programming - What is RxJS?
First thing need to understand is, Reactive programming is dealing with the event stream. Event stre ...
- JavaScript获取某年某月的最后一天
JavaScript获取某年某月的最后一天 1.实现源代码 <!DOCTYPE html> <!-- To change this license header, choose Li ...
- html_day2
总结下今天学的HTML知识.单词 跑马灯标记 <marquee></marquee>属性: direction:滚动的方向 取值:left .right. up. down b ...
- html_day1
第一天学习,了解到html的结构和语法. html的语法: 1.所有的html标签都要放在<>尖括号里. 2.标签不分大小写 建议小写 3.标签中的属性与标签名之间要有一个空格,如多个 ...
- document.write 存在几个问题?应该注意
document.write (henceforth DW) does not work in XHTML XHTML 不支持 DW executed after the page has finis ...
- 0119——UIImageView的一些属性 和 简单动画实现
1.contentMode view.contentMode = UIViewContentModeScaleAspectFill; 2.是否实现触摸 3.简单实现动画 图片的名字为campFire0 ...
- 转帖:深入理解JavaScript系列
感觉汤姆大叔这个系列写的很是不错,很适合有js基础但是想深入又无从下手的朋友. 深入理解JavaScript系列
- Mysql开发技巧之删除重复数据
Mysql利用联表查询和分组来删除重复数据 //删除表中重复的id,保留最大的id mysql> select * from user; +----+------+ | id | name | ...
- jQuery常用选择器汇总
一.基本选择器 <body> <div> <div id="div1"> aaaaaaaaaaa</div> <div cla ...