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) //释放指针申请的内存
搞清楚了上面的基本使用,你已经能够处理在Swift中遇到的大部分和指针相关的情况了。如果还有这里没有提及到的用法,你肯定能在猫神 onevcat 的Swift 中的指针使用中找到答案~
托管
托管解决的问题同样是来自C语言,在Cocoa中Core Fundation
框架就是封装的一套C语言API。如果在OC中使用Core Fundation
,可以参考这篇文章:说说Core Foundation。在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)
这个方法的使用场景在我的上一篇文章iOS图库大视频上传有提到过,还是挺有用处的,官方文档也有提及,但是相关资料非常少,所以想搞明白怎么用的小伙伴继续往下看喔,只此一家!
- 首先我们一个个参数分析
-
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语言的发展,这类问题我们会越来越少遇到,开发效率也会越来越高。但是目前我们在开发过程中还是总会碰到这样的问题,如果这篇文章对你有帮助的话,点一个喜欢和关注就是对我最大的鼓励啦!