美文网首页
Swift指针|内存管理

Swift指针|内存管理

作者: 精神薇 | 来源:发表于2022-07-27 16:56 被阅读0次

    一、Swift指针

    1.Swift指针简介

    swift中的指针分为两类

    • typed pointer 指定数据类型指针,即 UnsafePointer<T>,其中T表示泛型

    • raw pointer 未指定数据类型的指针(原生指针) ,即UnsafeRawPointer

    swift与OC指针对比如下:

    Swift OC 说明
    UnsafePointer<T> const T * 指针及所指向的内容都不可变
    UnsafeMutablePointer<T> T * 指针及其所指向的内存内容均可变
    UnsafeRawPointer const void * 指针指向不可变未知类型
    UnsafeMutableRawPointer void * 指针指向可变未知类型
    2.使用
    2.1 raw pointer

    注: 对于raw pointer,其内存管理是手动管理的,指针在使用完毕后需要手动释放

    // 定义一个未知类型的的指针p,分配32字节大小的空间,8字节对齐
    let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
    
    // 存储数据
    for i in 0..<4 {
        p.storeBytes(of: i + 1, as: Int.self)
    }
    
    // 读取数据
    for i in 0..<4 {
        // 从首地址开始偏移读取
        let value = p.load(fromByteOffset: i * 8, as: Int.self)
        print("index: \(i) value: \(value)")
    }
    
    // 释放内存
    p.deallocate()
    
    //index: 0 value: 4
    //index: 1 value: -5764607523034234880
    //index: 2 value: 4461297666
    //index: 3 value: 668503133614176
    

    从运行结果中可以看到,这并不是我们想要的结果,这也是我们平常在使用的时候需要特别注意的。因为我们在读取的时候是从指针首地址进行不断的偏移读取的,但是存储的时候却都是存储在了首地址,所以存储的时候也要进行偏移。修改后的代码如下:

    // 定义一个未知类型的的指针p,分配32字节大小的空间,8字节对齐
    let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
    
    // 存储数据
    for i in 0..<4 {
    //    p.storeBytes(of: i + 1, as: Int.self)
        // 修改后(每次存储的位置都有增加,也就是偏移)
        p.advanced(by: i * 8).storeBytes(of: i + 1, as: Int.self)
    }
    
    // 读取数据
    for i in 0..<4 {
        // 从首地址开始偏移读取
        let value = p.load(fromByteOffset: i * 8, as: Int.self)
        print("index: \(i) value: \(value)")
    }
    
    // 释放内存
    p.deallocate()
    
    
    //index: 0 value: 1
    //index: 1 value: 2
    //index: 2 value: 3
    //index: 3 value: 4
    

    allocate 源码

    /// Allocates uninitialized memory with the specified size and alignment.
    ///
    /// You are in charge of managing the allocated memory. Be sure to deallocate
    /// any memory that you manually allocate.
    ///
    /// The allocated memory is not bound to any specific type and must be bound
    /// before performing any typed operations. If you are using the memory for
    /// a specific type, allocate memory using the
    /// `UnsafeMutablePointer.allocate(capacity:)` static method instead.
    ///
    /// - Parameters:
    ///   - byteCount: The number of bytes to allocate. `byteCount` must not be negative.
    ///   - alignment: The alignment of the new region of allocated memory, in
    ///     bytes.
    /// - Returns: A pointer to a newly allocated region of memory. The memory is
    ///   allocated, but not initialized.
    @inlinable
    public static func allocate(
    byteCount: Int, alignment: Int
    ) -> UnsafeMutableRawPointer {
    // For any alignment <= _minAllocationAlignment, force alignment = 0.
    // This forces the runtime's "aligned" allocation path so that
    // deallocation does not require the original alignment.
    //
    // The runtime guarantees:
    //
    // align == 0 || align > _minAllocationAlignment:
    //   Runtime uses "aligned allocation".
    //
    // 0 < align <= _minAllocationAlignment:
    //   Runtime may use either malloc or "aligned allocation".
    var alignment = alignment
    if alignment <= _minAllocationAlignment() {
      alignment = 0
    }
    return UnsafeMutableRawPointer(Builtin.allocRaw(
        byteCount._builtinWordValue, alignment._builtinWordValue))
    }
    
    • 以指定的大小和对齐方式分配未初始化的内存
    • 首先对对齐方式进行校验
    • 然后调用Builtin.allocRaw方法进行分配内存
    • BuiltinSwift的标准模块,可以理解为调用(匹配)LLVM中的方法
    2.2 typed pointer

    定义

    /// Invokes the given closure with a pointer to the given argument.
    ///
    /// The `withUnsafePointer(to:_:)` function is useful for calling Objective-C
    /// APIs that take in parameters by const pointer.
    ///
    /// The pointer argument to `body` is valid only during the execution of
    /// `withUnsafePointer(to:_:)`. Do not store or return the pointer for later
    /// use.
    ///
    /// - Parameters:
    ///   - value: An instance to temporarily use via pointer.
    ///   - body: A closure that takes a pointer to `value` as its sole argument. If
    ///     the closure has a return value, that value is also used as the return
    ///     value of the `withUnsafePointer(to:_:)` function. The pointer argument
    ///     is valid only for the duration of the function's execution.
    ///     It is undefined behavior to try to mutate through the pointer argument
    ///     by converting it to `UnsafeMutablePointer` or any other mutable pointer
    ///     type. If you need to mutate the argument through the pointer, use
    ///     `withUnsafeMutablePointer(to:_:)` instead.
    /// - Returns: The return value, if any, of the `body` closure.
    @inlinable public func withUnsafePointer<T, Result>(to value: T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
    

    该函数一共两个参数:

    • 第一个就是要获取其指针的变量
    • 第二个是一个闭包,然后通过rethrows关键字重新抛出Result(也就是闭包表达式的返回值),闭包的参数和返回值都是泛型,关于这种写法可以缩写,详见后面的代码。
    var a = 10
    
    /**
        通过Swift提供的简写的API,这里是尾随闭包的写法
        返回值的类型是 UnsafePointer<Int>
     */
    let p = withUnsafePointer(to: &a) { $0 }
    print(p)
    
    withUnsafePointer(to: &a) {
        print($0)
    }
    
    // Declaration let p1:UnsafePointer<Int>
    let p1 = withUnsafePointer(to: &a) { ptr in
        return ptr
    }
    print(p1)
    //0x00007ff7ba577d18
    //0x00007ff7ba577d18
    //0x00007ff7ba577d18
    

    以上三种用法是我们最常用的三种方法,都能够打印出变量的指针。那么是否可以通过指针修改变量的值呢?下面我们就来研究一下:

    通过指针获取变量值

    要想改变值,首先就要能够访问到变量的值:

    let p = withUnsafePointer(to: &a) { $0 }
    print(p.pointee)
    
    withUnsafePointer(to: &a) {
        print($0.pointee)
    }
    
    let p1 = withUnsafePointer(to: &a) { ptr in
        return ptr
    }
    print(p1.pointee)
    
    let p2 = withUnsafePointer(to: &a) { ptr in
        return ptr.pointee
    }
    print(p2)
    //10
    //10
    //10
    //10
    

    通过指针修改变量值

    如果使用的是withUnsafePointer是不能直接在闭包中修改指针的,但是我们可以通过间接的方式,通过返回值修改,给原来变量赋值的方式修改(其实这种方式很low)

    a = withUnsafePointer(to: &a){ ptr in
        return ptr.pointee + 2
    }
    print(a)
    

    我们可以使用withUnsafeMutablePointer,直接修改变量的值。

    withUnsafeMutablePointer(to: &a){ ptr in
        ptr.pointee += 2
    }
    

    还有另一种方式,就是通过创建指针的方式,这也是一种创建Type Pointer的方式:

    // 创建一个指针,指针内存存储的是Int类型数据,开辟一个8*1字节大小的区域
    let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
    
    //初始化指针
    ptr.initialize(to: a)
    // 修改
    ptr.pointee += 2
    
    print(a)
    print(ptr.pointee)
    
    // 反初始化,与下面的代码成对调用,管理内存
    ptr.deinitialize(count: 1)
    // 释放内存
    ptr.deallocate()
    
    //10
    //12
    

    从这里我们可以看到,指针的值在修改后是变了的,但是原变量的值并没有改变。所以不能用于直接修改原变量。

    3.实例

    demo1

    本案例是初始化一个指针,能够访问两个结构体实例对象。

    首先定义一个结构体

    struct Teacher {
        var age = 18
        var height = 1.65
    }
    

    下面我们通过三种方式访问指针中的结构体对象

    • 通过下标访问
    • 通过内存平移访问
    • 通过successor()函数访问
    // 分配两个Teacher大小空间的指针
    let ptr = UnsafeMutablePointer<Teacher>.allocate(capacity: 2)
    
    // 初始化第一个Teacher
    ptr.initialize(to: Teacher())
    // 初始化第二个Teacher
    ptr.successor().initialize(to: Teacher(age: 20, height: 1.85))
    // 错误的初始化方式,因为这是确定类型的指针,只需移动一步即移动整个类型大小的内存
    //ptr.advanced(by: MemoryLayout<Teacher>.stride).initialize(to: Teacher(age: 20, height: 1.85))
    
    // 通过下标访问
    print(ptr[0])
    print(ptr[1])
    
    // 内存偏移
    print(ptr.pointee)
    print((ptr+1).pointee)
    
    // successor
    print(ptr.pointee)
    print(ptr.successor().pointee)
    
    // 反初始化,释放内存
    ptr.deinitialize(count: 2)
    ptr.deallocate()
    
    //Teacher(age: 18, height: 1.65)
    //Teacher(age: 20, height: 1.85)
    //Teacher(age: 18, height: 1.65)
    //Teacher(age: 20, height: 1.85)
    //Teacher(age: 18, height: 1.65)
    //Teacher(age: 20, height: 1.85)
    

    demo2

    下面我们就通过内存的方式,将实例对象绑定到我们自定义的HHObject上。

    struct HHObject{
        var kind: UnsafeRawPointer
    }
    
    class Teachers {
        var age: Int = 18
    }
    
    

    指针绑定:

    /**
             使用withUnsafeMutablePointer获取到的指针是UnsafeMutablePointer<T>类型
             UnsafeMutablePointer<T>没有bindMemory方法
             所以此处引入Unmanaged
             */
            //let ptr = withUnsafeMutablePointer(to: &t) { $0 }
    
            /**
             Unmanaged 指定内存管理,类似于OC与CF交互时的所有权转换__bridge
             Unmanaged 有两个函数:
             - passUnretained:不增加引用计数,也就是不获得所有权
             - passRetained:   增加引用计数,也就是可以获得所有权
             以上两个函数,可以通过toOpaque函数返回一个
             UnsafeMutableRawPointer 指针
            */
            let ptr4 = Unmanaged.passUnretained(Teachers()).toOpaque()
            //let ptr = Unmanaged.passRetained(t).toOpaque()
    
            /**
             bindMemory :将指针绑定到指定类型数据上
             如果没有绑定则绑定
             已绑定则重定向到指定类型上
            */
            let h = ptr4.bindMemory(to: HHObject.self, capacity: 1)
    
            print(h.pointee)
    
    
    //HHObject(kind: 0x00007efc5e4d0e60)
    

    demo3

    在实际开发中,我们往往会调用其他人的api完成一些代码,在指针操作过程中,往往会因为类型不匹配造成传参问题,下面我们就来看一个例子:

    首先我们定义一个打印指针的函数:

    func printPointer(p: UnsafePointer<Int>) {
        print(p)
        print("end")
    }
    

    示例代码:

    var tul = (10,20)
    withUnsafeMutablePointer(to: &tul) { tulPtr in
        printPointer(p: UnsafeRawPointer(tulPtr).assumingMemoryBound(to: Int.self))
    }
    
    //0x00007ff7b564cc60
    //end
    

    assumingMemoryBound是假定内存绑定,目的是告诉编译器已经绑定过Int类型了,不需要在检查内存绑定。

    那么我们将元组换成结构体呢?我们此时想要获取结构体中的属性。

    struct Test {
        var a: Int = 10
        var b: Int = 20
    }
    
    var t = Test()
    
    withUnsafeMutablePointer(to: &t) { ptr in
        printPointer(p: UnsafeRawPointer(ptr).assumingMemoryBound(to: Int.self))
    }
    //0x00007ff7b77b8c50
    //end
    

    那么如果我想想获取结构体中的属性呢?

    withUnsafeMutablePointer(to: &t) { ptr in
    //    let strongRefPtr = withUnsafePointer(to: &ptr.pointee.b) { $0 }
    //    printPointer(p: strongRefPtr)
    //    let strongRefPtr = UnsafeRawPointer(ptr).advanced(by: MemoryLayout<Int>.stride)
        let strongRefPtr = UnsafeRawPointer(ptr) - MemoryLayout<Test>.offset(of: \Test.b)!
        printPointer(p: strongRefPtr.assumingMemoryBound(to: Int.self))
    }
    

    以上提供了三种方式实现访问结构体中的属性。

    demo 4

    使用withMemoryRebound临时更改内存绑定类型,withMemoryRebound的主要应用场景还是处理一些类型不匹配的场景,将内存绑定类型临时修改成想要的类型,在闭包里生效,不会修改原指针的内存绑定类型。

    var age: UInt64 = 18
    
    let ptr = withUnsafePointer(to: &age) { $0 }
    // 临时更改内存绑定类型
    ptr.withMemoryRebound(to: Int.self, capacity: 1) { (ptr) in
        printPointer(p: ptr)
    }
    
    func printPointer(p: UnsafePointer<Int>) {
        print(p)
        print("end")
    }
    
    4.总结

    至此我们对Swift中指针的分析基本就完事了,现在总结如下:

    1. Swift中的指针分为两种:
    • raw pointer:未指定类型(原生)指针,即:UnsafeRawPointerunsafeMutableRawPointer
    • type pointer:指定类型的指针,即:UnsafePointer<T>UnsafeMutablePointer<T>
    1. 指针的绑定主要有三种方式:
    • withMemoryRebound:临时更改内存绑定类型
    • bingMemory(to: capacity:):更改内存绑定的类型,如果之前没有绑定,那么就是首次绑定,如果绑定过了,会被重新绑定为该类型
    • assumingMemoryBound:假定内存绑定,这里就是告诉编译器,我就是这种类型,不要检查了(控制权由我们决定)
    1. 内存指针的操作都是危险的,使用时要慎重

    二、内存管理

    跟OC一样,Swift也是采取基于引用计数的ARC内存管理方案(针对堆空间),Swift的ARC中有3种引用。


    1.弱引用
    protocol Livable : AnyObject {}
    class Person {}
    
    weak var p0: Person?
    weak var p1: AnyObject?
    weak var p2: Livable?
    
    unowned var p10: Person?
    unowned var p11: AnyObject?
    unowned var p12: Livable?
    

    说明:

    • weak、unowned只能用在类实例上面
    • 因为weak可以设置为nil,所以必须用可选项
    • 会发生改变,所以需要用var
    2.循环引用

    循环引用就是两个对象相互持有,无法释放会导致内存泄漏,和OC一样,所以重点看一下闭包的循环引用,其实也就和block一样
    循环引用的解决:weak、unowned 都能解决循环引用的问题,unowned 要比 weak 少一些性能消耗,在生命周期中可能会变为 nil 的使用 weak,初始化赋值后再也不会变为 nil 的使用unowned

    相关文章

      网友评论

          本文标题:Swift指针|内存管理

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