Swift指针和托管

作者: __Mr_Xie__ | 来源:发表于2020-01-16 12:14 被阅读0次

    前言

    想必已经使用 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) 申请 numT 类型的内存大小,返回指向该内存块的指针。
    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 语言,在 CocoaCore Fundation 框架就是封装的一套 C 语言 API。在Swift中使用Core Fundation,苹果提出了内存管理注释 annotated APIsUnmanaged<T> 泛型结构体结合的解决方案。

    • 对于 Core Fundation 中有 @annotated 注释的函数来说,返回的是托管对象,无需自己管理内存,可以直接获取到 CF 对象,并且可以无缝转化( toll free bridging )成 Fundation 对象,比如 NSStringCFString。目前,内存管理注释正在一步步的完善,所以等到未来某一个版本我们就可以完完全全的像使用 Fundation 一样使用 Core Fundation 啦。
    • 对于尚未注释的函数来说,苹果给出的是使用非托管对象 Unmanaged<T> 进行管理的过渡方案。
      当我们从 CF 函数中获取到 Unmanaged<T> 对象的时候,我们需要调用 takeRetainedValue 或者 takeUnretainedValue 获取到对象 T。具体使用哪一个方法,苹果提出了 Ownership Policy,具体来说就是:
      • 如果一个函数名中包含 CreateCopy,则调用者获得这个对象的同时也获得对象所有权,返回值 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)

    这个方法还是挺有用处的,官方文档也有提及,但是相关资料非常少,所以想搞明白怎么用的小伙伴继续往下看喔,只此一家!

    1. 首先我们一个个参数分析
    • CFAllocator在函数声明中已经有了详尽的解释,一般来说使用kCFAllocatorDefault
    • readStreamPointerwriteStreamPointer是一个指向非托管结构体类型Unmanaged<CFReadStream>?的指针,指向非托管的 CFReadStream 对象。
    • CFIndex可以toll free bridging转化为Int类型。
    1. 之前说过,使用指针之前需要初始化,所以我们先初始化指针。我们根据函数也可以判断,我们不需要申请一段内存,所以只需要申请一个Unmanaged<CFReadStream>?大小的内存就好了。
    let readStreamPointer = UnsafeMutablePointer<Unmanaged<CFReadStream>?>.alloc(1)
    let writeStreamPointer = UnsafeMutablePointer<Unmanaged<CFWriteStream>?>.alloc(1)
    
    1. 由于我们的指针是可变的,memory 可以被赋值,所以调用方法 CFStreamCreateBoundPair 之后,memory 就被赋值为了创建的Unmanaged<CFReadStream>? 类型的非托管对象。我们可以通过 memory 取到这个非托管对象。根据 Create rules,我们应该使用 takeRetainedValue() 获取到 CFReadStream 类型的对象,这时候非托管对象已经把对象的管理权交由给了 SwiftARC 管理。NSStreamCFStream 之间是 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
    }
    
    1. 释放指针申请的内存空间,与 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开发者进阶,直接留言,留言必回。

    转载

    Swift指针和托管,你看我就够了

    相关文章

      网友评论

        本文标题:Swift指针和托管

        本文链接:https://www.haomeiwen.com/subject/ctorgctx.html