Swift指针和托管
前言
想必已经使用 Swift
语言进行开发的小伙伴们都享受到了这门语言在开发过程中带来的便利,确实作为苹果官方主推的编程语言,融合了主流编程语言的优点,旨在提高开发效率,已经开始逐步走上替代 OC
的道路了。然而不是事事尽如人意,Swift
语言也存在一些不足,其中最主要的问题有两个:
-
Swift
语言从1.0
开始到现在的2.2
一直处于改进之中,这种改进可能对于语言本身来说是一种改进,但对于使用者来说简直是噩梦。。。每一次发布新版本,xcode
一片一片的飘红飘黄绝对会让你抓狂。这种情况在Swift2.0
以后有了一定的好转,可后面不是还有3.0
么...不说了蹲厕所哭一会T_T - 由于
OC
原生支持C
语言,Cocoa
框架是OC
编写的,这种历史包袱使得Swift
也必须支持C
语言,这就引入了接下来讨论的问题,指针UnsafePointer
和托管Unmanaged
指针
在 Swift
中,指针都是以一个泛型结构体 UnsafePointer<T>
表示,遵从 Cocoa
一贯的设计原则,它是不可变的,与之对应的就是可变指针 UnsafeMutablePointer<T>
。这两个泛型分别可以对应 C
语言中的常指针 const void *
和普通指针 void *
。我们通常来说可以通过 &
操作从一个 Cocoa
类型T获取到其指针表示 UnsafePointer<T>
,这一点和 C
语言基本一致,有一定的不同。最典型的就是在 Swift2.0
之前传递 NSError
的指针 NSErrorPointer
。
var error: NSError?
var possibleData =NSJSONSerialization.JSONObjectWithData(responseData,options:NSJSONReadingOptions.AllowFragments,error: &error) as? NSDictionary;
if let actualError = error {
println("An Error Occurred: \\\\(actualError)")
}
else if let data = possibleData {
// do something with the returned data
}
但是 Swift
的 &
操作和 C
语言不同的一点是,Swift
不允许直接获取对象的指针,比如下面的代码就会编译不通过。
let a = NSData()
let b = &a //编译出错
指针的管理和其他对象的管理是不同的,其他的对象是 ARC
管理的,而指针需要我们手动地申请和释放内存,指针基本的使用步骤如下:
- 申请内存块,使用
UnsafePointer<T>.alloc(num:Int)
申请num
个T
类型的内存大小,返回指向该内存块的指针。
var p = UnsafePointer<NSData>.alloc(1)
- 初始化指针指向的内存,
T
如果是值类型直接初始化,如果是类类型则先创建对象,用对象初始化。
let data = NSMutableData()
p.initialize(data)
- 使用
memory
访问指向的内存(注意:不是指向的对象,初始化的过程其实是把上面的data
拷贝到了指向的内存块中,所以p
指向的内存已经和data
没有关系了)。- 对于
UnsafePointer<T>
类型,内存初始化之后就不能再改变指向的内存。 - 对于
UnsafeMutablePointer<T>
类型,内存初始化之后还可以通过memory
改变指向的内存。
- 对于
- 释放指针指向的对象和指针申请的内存
p.destroy() //释放指向的对象
p.dealloc(1) //释放指针申请的内存
托管
托管解决的问题同样是来自 C
语言,在 Cocoa
中 Core Fundation
框架就是封装的一套 C
语言 API
。在Swift中使用Core Fundation
,苹果提出了内存管理注释 annotated APIs
和 Unmanaged<T>
泛型结构体结合的解决方案。
- 对于
Core Fundation
中有@annotated
注释的函数来说,返回的是托管对象,无需自己管理内存,可以直接获取到CF
对象,并且可以无缝转化(toll free bridging
)成Fundation
对象,比如NSString
和CFString
。目前,内存管理注释正在一步步的完善,所以等到未来某一个版本我们就可以完完全全的像使用Fundation
一样使用Core Fundation
啦。 - 对于尚未注释的函数来说,苹果给出的是使用非托管对象
Unmanaged<T>
进行管理的过渡方案。
当我们从CF
函数中获取到Unmanaged<T>
对象的时候,我们需要调用takeRetainedValue
或者takeUnretainedValue
获取到对象T
。具体使用哪一个方法,苹果提出了 Ownership Policy,具体来说就是:- 如果一个函数名中包含
Create
或Copy
,则调用者获得这个对象的同时也获得对象所有权,返回值Unmanaged
需要调用takeRetainedValue()
方法获得对象。调用者不再使用对象时候,Swift
代码中不需要调用CFRelease
函数放弃对象所有权,这是因为Swift
仅支持ARC
内存管理,这一点和OC
略有不同。 - 如果一个函数名中包含
Get
,则调用者获得这个对象的同时不会获得对象所有权,返回值Unmanaged
需要调用takeUnretainedValue()
方法获得对象。 示例代码如下:
- 如果一个函数名中包含
let bestFriendID = ABRecordID(...)
// Create Rule - retained
let addressBook: ABAddressBook = ABAddressBookCreateWithOptions(nil, nil).takeRetainedValue()
// Get Rule - unretained
if let bestFriendRecord: ABRecord = ABAddressBookGetPersonWithRecordID(addressBook, bestFriendID)?.takeUnretainedValue() {
// Create Rule (Copy) - retained
if let name = ABRecordCopyCompositeName(bestFriendRecord)?.takeRetainedValue() as? String {
//do something
}
}
练习
相信各位小伙伴如果把下面这个 CF
函数搞明白了,也就弄懂了 Swift
的指针和托管,Let's do it!
public func CFStreamCreateBoundPair(alloc: CFAllocator!, _ readStream: UnsafeMutablePointer<Unmanaged<CFReadStream>?>, _ writeStream: UnsafeMutablePointer<Unmanaged<CFWriteStream>?>, _ transferBufferSize: CFIndex)
这个方法还是挺有用处的,官方文档也有提及,但是相关资料非常少,所以想搞明白怎么用的小伙伴继续往下看喔,只此一家!
- 首先我们一个个参数分析
-
CFAllocator
在函数声明中已经有了详尽的解释,一般来说使用kCFAllocatorDefault
。 -
readStreamPointer
和writeStreamPointer
是一个指向非托管结构体类型Unmanaged<CFReadStream>?
的指针,指向非托管的CFReadStream
对象。 -
CFIndex
可以toll free bridging
转化为Int
类型。
- 之前说过,使用指针之前需要初始化,所以我们先初始化指针。我们根据函数也可以判断,我们不需要申请一段内存,所以只需要申请一个
Unmanaged<CFReadStream>?
大小的内存就好了。
let readStreamPointer = UnsafeMutablePointer<Unmanaged<CFReadStream>?>.alloc(1)
let writeStreamPointer = UnsafeMutablePointer<Unmanaged<CFWriteStream>?>.alloc(1)
- 由于我们的指针是可变的,
memory
可以被赋值,所以调用方法CFStreamCreateBoundPair
之后,memory
就被赋值为了创建的Unmanaged<CFReadStream>?
类型的非托管对象。我们可以通过memory
取到这个非托管对象。根据Create rules
,我们应该使用takeRetainedValue()
获取到CFReadStream
类型的对象,这时候非托管对象已经把对象的管理权交由给了Swift
的ARC
管理。NSStream
和CFStream
之间是toll free bridging
。
CFStreamCreateBoundPair(kCFAllocatorDefault, readStreamPointer,writeStreamPointer, Int(bufferSize) as CFIndex)
if let readStream = readStreamPointer.memory?.takeRetainedValue(),writeStream = writeStreamPointer.memory?.takeRetainedValue(){// create rules
let rStream = readStream as NSInputStream
let wStream = writeStream as NSOutputStream //toll free bridging
//do something with rStream/wStream
}
- 释放指针申请的内存空间,与
alloc
对应的delloc
。
readStreamPointer.dealloc(1)
writeStreamPointer.dealloc(1)
完整的代码如下:
let readStreamPointer = UnsafeMutablePointer<Unmanaged<CFReadStream>?>.alloc(1)
let writeStreamPointer = UnsafeMutablePointer<Unmanaged<CFWriteStream>?>.alloc(1)
CFStreamCreateBoundPair(kCFAllocatorDefault, readStreamPointer,writeStreamPointer, Int(bufferSize) as CFIndex)
if let readStream = readStreamPointer.memory?.takeRetainedValue(),writeStream = writeStreamPointer.memory?.takeRetainedValue(){// create rules
let rStream = readStream as NSInputStream
let wStream = writeStream as NSOutputStream //toll free bridging
//do something with rStream/wStream
}
readStreamPointer.dealloc(1)
writeStreamPointer.dealloc(1)
总结
指针和托管在 Swift
语言的发展过程中起到了兼容和过渡的作用,相信随着 Swift
语言的发展,这类问题我们会越来越少遇到,开发效率也会越来越高。但是目前我们在开发过程中还是总会碰到这样的问题,如果这篇文章对你有帮助的话,点一个喜欢和关注就是对我最大的鼓励啦!
Author
如果你有什么建议,可以关注我的公众号:iOS开发者进阶
,直接留言,留言必回。
