美文网首页
内存管理

内存管理

作者: 三国韩信 | 来源:发表于2021-03-19 17:19 被阅读0次

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

    Swift的ARC中有3种引用:

    1、强引用(strong reference):默认情况下,引用都是强引用;
    2、弱引用(weak reference):通过weak定义弱引用,必须是可选类型的var,因为实例销毁后,ARC会自动将弱引用设置为nil。ARC自动给弱引用设置nil时,不会触发属性观察器;
    3、无主引用(unowned reference):通过unowned定义无主引用,不会产生强引用,实例销毁后仍然存储着实例的内存地址(类似于OC中的unsafe_unretained)。试图在实例销毁后访问无主引用,会产生运行时错误(野指针);

    注:weak和unowned的区别是,weak在对象销毁后比自动置为nil,unowned不会。正因为weak会被自动置为nil,所以必须是var类型,不能是let(let不可变),同时也必须是可选类型,因为可选类型才能置为nil。这也是为啥在block里用weak后,self?.xxx 访问的缘故。

    block中为啥要用self.访问

    编译器强制要求在block中访问必须用self.是为了提醒开发者这里有可能会有循环引用的风险,是不是要用weak来修饰。仅仅是为了提醒开发者而做的编译器校验而已。

    指针

    Swift中也有专门的指针类型,这些都被定性为“Unsafe”(不安全的),常见的有以下4种类型:
    1、UnsafePointer<Pointee> 类似于 const Pointee *
    2、UnsafeMutablePointer<Pointee> 类似于 Pointee *
    3、UnsafeRawPointer 类似于 const void *
    4、UnsafeMutableRawPointer 类似于 void *

    var age = 10
    func test1(_ ptr: UnsafeMutablePointer<Int>) {
        ptr.pointee += 10
    }
    func test2(_ ptr: UnsafePointer<Int>) { 
        print(ptr.pointee)
    }
    test2(&age) // 10
    test1(&age) // 20
    print(age) // 20
    
    var age = 10
    func test3(_ ptr: UnsafeMutableRawPointer) {
        ptr.storeBytes(of: 20, as: Int.self) 
    }
    func test4(_ ptr: UnsafeRawPointer) { 
        print(ptr.load(as: Int.self))
    }
    test4(&age) //10
    test3(&age) // 20
    print(age) // 20
    
    更多使用例子
    var age: Int = 10
    var age1: Float = 10.1
    
    func getPtr<T>(ptr: UnsafePointer<T>) -> UnsafePointer<T> {
        return ptr
    }
    let p = getPtr(ptr: &age)
    print(p.pointee)
    
    
    func getPtr1<T>(ptr: UnsafePointer<T>,body:(UnsafePointer<T>) -> T) -> T {
        return body(ptr)
    }
    let p2 = getPtr1(ptr: &age1) { (p) -> Float in
        return 10.0
    }
    print(p2)
    
    
    
    func getPtr2<T,Result>(ptr: UnsafePointer<T>,body:(UnsafePointer<T>) -> Result) -> Result {
        return body(ptr)
    }
    let result = getPtr2(ptr: &age) { (p) -> UnsafeRawPointer in
        let rp = UnsafeRawPointer(p)
        return rp
    }
    print(result.load(as: Int.self))
    
    
    class Student {
        var age: Int = 20
    }
    
    var s: Student = Student()
    
    let studentPtr1 = withUnsafePointer(to: &s){$0}
    print(studentPtr1.pointee.age)
    
    // studentPtr是s这个变量的地址值(不是student堆空间的地址值)
    let studentPtr = withUnsafePointer(to: &s) {UnsafeRawPointer($0)}
    var address = studentPtr.load(as: UInt.self)
    // student 堆空间的地址值
    let student_dui_ptr = UnsafeMutableRawPointer(bitPattern: address)
    print(student_dui_ptr!)
    //取出student变量从第16字节开始的数据,也就是age的值。
    print(student_dui_ptr?.load(fromByteOffset: 16, as: Int.self))
    // 指针往后走16个字节,然后取值
    let student_dui_ptr_2 = student_dui_ptr?.advanced(by: 16)
    print(student_dui_ptr_2?.load(as: Int.self))
    
    Struct

    在swift中,结构体(Struct)是值类型,不是引用类型。一个结构体对象在内存中占据的大小由结构体的成员变量来决定。
    值类型赋值给var、let或者给函数传参,是直接将所有内容拷贝一份(深拷贝)
    在swift中,Array、Dictionary、String、Set都是结构体,故他们是值类型(这一点和OC不太一样)

    struct Point {
        var x: Int
        var y: Int
    }
    var p: Point = Point(x: 10, y: 20)
    print(MemoryLayout<Point>.size) // 16
    print(MemoryLayout<Point>.stride) // 16
    print(MemoryLayout<Point>.alignment) // 8
    /*
    size:表示结构体的内存大小;
    stride:表示系统最终分配给他的内存大小;
    alignment:表示以多少字节对齐(比如8字节对齐)。
    */
    
    struct Size {
        var width: Int
        var height: Int
        var change: Bool
    }
    
    var p: Size = Size(width: 10, height: 20,change: true)
    print(MemoryLayout<Size>.size) // 17
    print(MemoryLayout<Size>.stride) // 24
    print(MemoryLayout<Size>.alignment) // 8
    // Size结构体总共需要17个字节(8+8+1),由于是8字节对齐,最终系统会分配24个字节给它
    
    Class

    在swift中,类(Class)是引用类型,不是值类型。一个类的对象在内存中占据的大小由类的存储属性大小再加上16个字节。这多出来的16个字节是在最前面的,分别存指向类型的信息(元类)和引用计数器。
    引用类型赋值给var、let或者给函数传参,是将内存地址拷贝一份 (浅拷贝/引用拷贝)

      class Size {
            var width: Int
            var height: Int
            init(width: Int, height: Int) {
                self.width = width
                self.height = height
            }
        }
      let pc = Size(width: 1, height: 2)
    /* 类的内存布局如下图,在堆空间,占32个字节,前8个字节放指向类型信息,接着8个字节放引用计数器,
    剩下的16个字节分别放width和height的值 */
    
    image.png

    引用类型不能像值类型那样通过MemoryLayout得到对象的内存大小,因为类的对象是初始化在堆空间的,而指向这个空间的变量有可能是栈空间的地址或全局变量地址。通过MemoryLayout得出的大小永远是8字节(因为一个指针变量就是8字节)。如果要获取引用类型对象的真正堆空间的大小,要用malloc_size函数去获取。代码如下:

       class Size {
            var width: Int
            var height: Int
            init(width: Int, height: Int) {
                self.width = width
                self.height = height
            }
        }
        var pc = Size(width: 39, height: 34)
        print(MemoryLayout<Size>.size(ofValue: pc)) // 8
        print(MemoryLayout<Size>.stride(ofValue: pc)) // 8
        print(MemoryLayout<Size>.alignment) // 8
        // 上面的MemoryLayout 的结果永远是8字节
    
        let pr = withUnsafePointer(to: &pc) {UnsafeRawPointer($0)}
        // 获得指向pc对象的指针
        print(pr.load(as: UInt.self))
        // 获得pc对象指向堆空间的地址
        let address = UnsafeRawPointer(bitPattern: pr.load(as: UInt.self))
        // 获得堆空间的地址转成UnsafeRawPointer指针
        print(address)
        print(malloc_size(address!)) // 通过这个c函数获取地址大小,结果为32个字节
    

    相关文章

      网友评论

          本文标题:内存管理

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