Swift值类型和引用类型

最近在swit的开发过程中,碰到了一个糗事,定义一个新的变量去接收我数据请求返回的model,而该model是用struct定义的,结果在对新的变量进行值的修改后,发现原来的model数据并没有修改,也就是说做了一次深拷贝操作。问题解决后,对于 Swift 中的 struct,class 以及 enum 在一般的使用中能够做到互相替换

由于 Swift 中的 struct 为值类型,class 为引用类型,因此文中以这两种类型为代表来具体阐述

stack & heap

内存(RAM)中有两个区域,栈区(stack)和堆区(heap)。在 Swift 中,值类型,存放在栈区;引用类型,存放在堆区。

值类型 & 引用类型

1 值类型

值类型,即每个实例保持一份数据拷贝

在 Swift 中,典型的有 struct,enum,以及 tuple 都是值类型。而平时使用的 Int, Double,Float,String,Array,Dictionary,Set 其实都是用结构体实现的,也是值类型。
Swift 中,值类型的赋值为深拷贝(Deep Copy),值语义(Value Semantics)即新对象和源对象是独立的,当改变新对象的属性,源对象不会受到影响,反之同理

struct MHFStruct {
    var x : Double
    var y : Double
}
  var cordA = MHFStruct(x: 3, y: 4)
        var cordB = cordA
        
        cordA.x = 1000
        print("cordA.x ->\(cordA.x)")
        print("cordB.x->\(cordB.x)")
        
        
        withUnsafePointer(to: &cordA, {print("\($0)")})
        withUnsafePointer(to: &cordB, {print("\($0)")})
        
  
 
        /**
         cordA.x ->1000.0
         cordB.x->3.0
         0x00007ffee92897c0
         0x00007ffee92897b0
         */

在 Swift 3.0 中,可以使用 withUnsafePointer(to:_😃 函数来打印值类型变量的内存地址,这样就能看出两个变量的内存地址并不相同。

2 引用类型

引用类型,即所有实例共享一份数据拷贝。

在 Swift 中,class 和闭包是引用类型。引用类型的赋值是浅拷贝(Shallow Copy),引用语义(Reference Semantics)即新对象和源对象的变量名不同,但其引用(指向的内存空间)是一样的,因此当使用新对象操作其内部数据时,源对象的内部数据也会受到影响。


class MHFDog {
    var height = 0.0
    var wight = 0.0
}

     var dogA = MHFDog()
        var dogB = dogA
        dogA.height = 50;
        print("dogA.height ->\(dogA.height)")
        print("dogB.height->\(dogB.height)")
   
        print(Unmanaged.passUnretained(dogA).toOpaque())
         print(Unmanaged.passUnretained(dogB).toOpaque())
        /**
         dogA.height ->50.0
         dogB.height->50.0
         0x000060000025b9c0
         0x000060000025b9c0
         
         */

在 Swift 3.0 中,可以使用Unmanaged.passUnretained方法来打印引用类型变量指向的内存地址。从中即可发现,两个变量指向的是同一块内存空间。

函数传参问题

在 Swift 中,函数的参数默认为常量,即在函数体内只能访问参数,而不能修改参数值。具体来说:

值类型作为参数传入时,函数体内部不能修改其值

引用类型作为参数传入时,函数体内部不能修改其指向的内存地址,但是可以修改其内部的变量值

定义一个 ResolutionStruct 结构体,以及一个 ResolutionClass 类。这里为了方便打印对象属性,ResolutionClass 类遵从了 CustomStringConvertible 协议。

struct ResolutionStruct {
    var height = 0.0
    var width = 0.0
}

class ResolutionClass: CustomStringConvertible {
    var height = 0.0
    var width = 0.0
    
    var description: String {
        return "ResolutionClass(height: \(height), width: \(width))"
    }
}
func swap(resSct: ResolutionStruct) -> ResolutionStruct {
            var resSct = resSct
            withUnsafePointer(to: &resSct) { print("During calling: \($0)") }
            
            let temp = resSct.height
            resSct.height = resSct.width
            resSct.width = temp
            
            return resSct
        }

        var iPhone4ResoStruct = ResolutionStruct(height: 960, width: 640)
        print(iPhone4ResoStruct)
        withUnsafePointer(to: &iPhone4ResoStruct) { print("Before calling: \($0)") }
        print(swap(resSct: iPhone4ResoStruct))
        print(iPhone4ResoStruct)
        withUnsafePointer(to: &iPhone4ResoStruct) { print("After calling: \($0)") }

        /*
         ResolutionStruct(height: 960.0, width: 640.0)
         Before calling: 0x00007ffee26e07c0
         During calling: 0x00007ffee26e0680
         ResolutionStruct(height: 640.0, width: 960.0)
         ResolutionStruct(height: 960.0, width: 640.0)
         After calling: 0x00007ffee26e07c0

         */

小结:在调用函数前后,外界变量值并没有因为函数内对参数的修改而发生变化,而且函数体内参数的内存地址与外界不同。因此:当值类型的变量作为参数被传入函数时,相当于创建了新的常量并初始化为传入的变量值,该参数的作用域及生命周期仅存在于函数体内。

2 引用类型

 func swap(resCls: ResolutionClass) {
            print("During calling: \(Unmanaged.passUnretained(resCls).toOpaque())")
            let temp = resCls.height
            
            resCls.height = resCls.width
            resCls.width = temp
        }

        let iPhone5ResoClss = ResolutionClass()
        iPhone5ResoClss.height = 1136
        iPhone5ResoClss.width = 640
        print(iPhone5ResoClss)
        print("Before calling: \(Unmanaged.passUnretained(iPhone5ResoClss).toOpaque())")
        swap(resCls: iPhone5ResoClss)
        print(iPhone5ResoClss)
        print("After calling: \(Unmanaged.passUnretained(iPhone5ResoClss).toOpaque())")

        /**
         ResolutionClass(height: 1136.0, width: 640.0)
         Before calling: 0x0000600000061f20
         During calling: 0x0000600000061f20
         ResolutionClass(height: 640.0, width: 1136.0)
         After calling: 0x0000600000061f20
         
         
         */

小结:在调用函数前后,外界变量值随函数内对参数的修改而发生变化,而且函数体内参数的内存地址与外界一致。因此:当引用类型的变量作为参数被传入函数时,相当于创建了新的常量并初始化为传入的变量引用,当函数体内操作参数指向的数据,函数体外也受到了影响。

3 .inout
inout 是 Swift 中的关键字,可以放置于参数类型前,冒号之后。使用 inout 之后,函数体内部可以直接更改参数值,而且改变会保留。

   func swap(resSct: inout ResolutionStruct) -> ResolutionStruct {
            var resSct = resSct
            withUnsafePointer(to: &resSct) { print("During calling: \($0)") }
            
            let temp = resSct.height
            resSct.height = resSct.width
            resSct.width = temp
            
            return resSct
        }

        var iPhone4ResoStruct = ResolutionStruct(height: 960, width: 640)
        print(iPhone4ResoStruct)
        withUnsafePointer(to: &iPhone4ResoStruct) { print("Before calling: \($0)") }
        print(swap(resSct: &iPhone4ResoStruct))
        print(iPhone4ResoStruct)
        withUnsafePointer(to: &iPhone4ResoStruct) { print("After calling: \($0)") }

        /*
         ResolutionStruct(height: 960.0, width: 640.0)
         Before calling: 0x00007ffee304d7c0
         During calling: 0x00007ffee304d670
         ResolutionStruct(height: 640.0, width: 960.0)
         ResolutionStruct(height: 960.0, width: 640.0)
         After calling: 0x00007ffee304d7c0

         */

小结:值类型变量作为参数传入函数,外界和函数参数的内存地址一致,函数内对参数的更改得到了保留。
引用类型也可以使用 inout 参数,但意义不大。

使用 inout 关键字的函数,在调用时需要在该参数前加上 & 符号

inout 参数在传入时必须为变量,不能为常量或字面量(literal)

inout 参数不能有默认值,不能为可变参数

inout 参数不等同于函数返回值,是一种使参数的作用域超出函数体的方式

多个 inout 参数不能同时传入同一个变量,因为拷入拷出的顺序不定,那么最终值也不能确定

inout 参数的传递过程:

当函数被调用时,参数值被拷贝

在函数体内,被拷贝的参数修改

函数返回时,被拷贝的参数值被赋值给原有的变量

嵌套类型

1.值类型嵌套值类型

值类型嵌套值类型时,赋值时创建了新的变量,两者是独立的,嵌套的值类型变量也会创建新的变量,这两者也是独立的。

2.值类型嵌套引用类型

值类型嵌套引用类型时,赋值时创建了新的变量,两者是独立的,但嵌套的引用类型指向的是同一块内存空间,当改变值类型内部嵌套的引用类型变量值时(除了重新初始化),其他对象的该属性也会随之改变。

3.引用类型嵌套值类型

引用类型嵌套值类型时,赋值时创建了新的变量,但是新变量和源变量指向同一块内存,因此改变源变量的内部值,会影响到其他变量的值。

4.引用类型嵌套引用类型

引用类型嵌套引用类型时,赋值时创建了新的变量,但是新变量和源变量指向同一块内存,内部引用类型变量也指向同一块内存地址,改变引用类型嵌套的引用类型的值,也会影响到其他变量的值。

参考这篇很棒

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值