Swift总的来说是一门比较容易编写的静态编译且略带一些动态特性的编程语言。由于Swift在2014年才刚诞生,因此当前在语法上修改的幅度比较大,尤其是从Swift 1.0到Swift 2.0;Swift 2.0到Swift 3.0。然而,此编程语言也逐步走入正轨,当前所有的改动都准对编程语言的稳定性、语法体系的完备性、API命名规则的一致性等问题进行展开,并且Swift从一开始就在保留对Objective-C以及C语言的相兼容性。我们在Swift中可以使用大量C语言形式的系统API,这些API中有不少包含了指针参数,因此这篇博文将给大家介绍在于C API进行交互时,Swift 2.2如何妥善处理指针的问题。
首先给大家的建议时,当我们现在用Swift时,如果有些算法或功能模块需要用C语言完成,那么涉及到函数回调的情况时请优先使用Blocks语法而不是函数指针。Blocks语法是Apple在LLVM开源项目中为Clang编译器提供的Lambda表达式,可用于纯C语言、C++、以及Objective-C/C++。由于Swift编程语言中的函数以及闭包是与Blocks无缝兼容的,此外Blocks与传统的函数调用比起来有许多强大、灵活的地方,想必各位在使用时已经能体会到这一点了,呼呼~
那么下面我们就说一下Swift在使用异步回调API时可能会发生的问题。在Apple官方的《Using Swift with Cocoa and Objective-C》一书中已经明确谈到——传递给函数的指针只有在函数调用期间才确保是有效的。当函数返回之后,不要设法去持有它并访问它。这句话很明确地表述了,在Swift中只有在调用函数的生命周期里,传递给它的指针对象才是有效的,当函数返回之后就无法保证该指针还有效。这里,笔者还曾给Apple提过一个bug(https://bugs.swift.org/browse/SR-1983),Apple本部的Swift开发工程师也是耐心地跟我解释,后来再次查阅《Using Swift with Cocoa and Objective-C》一书时,果然有这句话存在!所以通过以下这种函数去hold着指针对象是不可靠的:
public func getMutablePointer<T>(ptr: UnsafeMutablePointer<T>) -> UnsafeMutablePointer<T> { return ptr } class ViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() var obj = 10 let p = getMutablePointer(&obj) // 指针对象p在这个位置起就无法保证其有效性 print("obj = \(p[0])") } }
尽管一般情况下,上述代码能得到可预期的结果,但不建议这么使用。与此同时,我们可以看到,Swift中提供的UnsafePointer与UnsafeMutablePointer这两个类中也没有直接提供将一个对象直接转为一个指针的初始化器或其它类方法和成员方法。指针只能通过函数调用或方法调用的形式得到!
由此可知,我们在Swift中尽量使用更上层的API,如果在C语言层涉及到函数回调等情况,也尽量使用Blocks。下面我们将演示一段Swift中使用pthread以及Grand Central Dispatch来做多线程并行计算的代码:
/** 线程执行例程<br/> 这里使用private仅仅表示该函数仅在此Swift文件内可见 - parameters: - arg: 线程执行的环境参数 - returns: 返回一个void*指针对象,这里将直接返回空 */ private func threadProc(arg: UnsafeMutablePointer<Void>) -> UnsafeMutablePointer<Void> { // 这里arg是指向一个ViewController对象的指针 if arg == nil { return nil } let obj = UnsafeMutablePointer<ViewController>(arg).memory obj.test() return nil } class ViewController: NSViewController { private func test() { print("This is a test!") } override func viewDidLoad() { super.viewDidLoad() // 由于self是常量对象,不能作为引用操作符&的操作数 // 因此这里再定义一个var变量 var ptr = self // 这里各位需要注意,由于这里想让threadID初始化为空, // 但它作为pthread_create的参数不被允许是optional的, // 因此这里笔者采用了直接上值为0的指针这种巧妙的手法来对它初始化为空 var threadID: pthread_t = pthread_t(bitPattern: 0) // 尽管threadID它不是一个optional类型,但仍然可以与nil进行比较, // 并且这里确实比较结果为空! if threadID == nil { print("null threadID!") } // 这种方式不太可取 pthread_create(&threadID,nil,threadProc,&ptr) // 下面直接使用Grand Central Dispatch的方式做多线程计算,推荐使用! dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY,0)) { self.test() } } }
这里需要提醒各位的是,在Swift中只有函数以及不捕获外部对象的闭包才能作为C函数指针的实参,类的成员方法、以及捕获外部对象的闭包都不能作为C函数指针的实参!
当然,如果各位想要更体面地使用pthread也不是没有办法,这时大家可以通过直接提供所要使用对象的类属性或类方法即可,然后在线程执行例程中进行调用。比如以下代码所示:
/**
线程执行例程<br/>
这里使用private仅仅表示该函数仅在此Swift文件内可见
- parameters:
- arg: 线程执行的环境参数
- returns: 返回一个void*指针对象,这里将直接返回空
*/
private func threadProc(arg: UnsafeMutablePointer<Void>) -> UnsafeMutablePointer<Void> {
guard let obj = ViewController.theInstance else {
return nil
}
obj.test()
return nil
}
class ViewController: NSViewController {
private func test() {
print("This is a test!")
}
/// 此单利仅在线程调度前被初始化
private static var theInstance: ViewController?
override func viewDidLoad() {
super.viewDidLoad()
// 这里各位需要注意,由于这里想让threadID初始化为空,
// 但它作为pthread_create的参数不被允许是optional的,
// 因此这里笔者采用了直接上值为0的指针这种巧妙的手法来对它初始化为空
var threadID: pthread_t = pthread_t(bitPattern: 0)
// 在做线程调度之前为theInstance进行初始化
ViewController.theInstance = self
pthread_create(&threadID,nil,threadProc,nil)
}
}
这么一来,我们就无需为所传递的指针实参是否有效而抓头皮了。