美文网首页
Swift 中的值类型与引用类型

Swift 中的值类型与引用类型

作者: Jorunk | 来源:发表于2018-07-07 16:09 被阅读10次

    作者:萌面大道
    链接:https://www.jianshu.com/p/ba12b64f6350

    Swift 中的 struct 为值类型,class 为引用类型

    stack & heap

    • 内存(RAM)中有两个区域,栈区(stack)和堆区(heap)。在 Swift 中,值类型,存放在栈区;引用类型,存放在堆区。
    class DemoClass {
        var height = 0.0
        var width = 0.0
    }
    
    struct DemoStruct {
        var height = 0.0
        var width = 0.0
    }
    
    var rectCls = DemoClass()
    var rectStrct = DemoStruct()
    
    

    值类型 & 引用类型

    值类型(Value Type)
    • 值类型,即每个实例保持一份数据拷贝

    在 Swift 中,典型的有 struct,enum,以及 tuple 都是值类型。而平时使用的 IntDoubleFloatStringArrayDictionarySet 其实都是用结构体实现的,也是值类型。

    值类型
    struct CoordinateStruct {
        var x: Double
        var y: Double
    }
    
    var coordA = CoordinateStruct(x: 0, y: 0)
    var coordB = coordA
    
    coordA.x = 100.0
    print("coordA.x -> \(coordA.x)")
    print("coordB.x -> \(coordB.x)")
    
    // coordA.x -> 100.0
    // coordB.x -> 0.0
    
    

    如果声明一个值类型的常量,那么就意味着该常量是不可变的(无论内部数据为 var/let)。

    let coordC = CoordinateStruct(x: 0, y: 0)
    // WRONG: coordC.x = 100.0
    
    • 在 Swift 3.0 中,可以使用 withUnsafePointer(to:_:) 函数来打印值类型变量的内存地址,这样就能看出两个变量的内存地址并不相同。
    withUnsafePointer(to: &coordA) { print("\($0)") }
    withUnsafePointer(to: &coordB) { print("\($0)") }
    
    // 0x000000011df6ec10
    // 0x000000011df6ec20
    

    在 Swift 中,双等号(== & !=)可以用来比较变量存储的内容是否一致,如果要让我们的 struct 类型支持该符号,则必须遵守Equatable协议。

    extension CoordinateStruct: Equatable {
        static func ==(left: CoordinateStruct, right: CoordinateStruct) -> Bool {
            return (left.x == right.x && left.y == right.y)
        }
    }
    
    if coordA != coordB {
        print("coordA != coordB")
    }
    
    // coordA != coordB
    
    

    引用类型(Reference Type)

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

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

    引用类型
    class Dog {
        var height = 0.0
        var weight = 0.0
    }
    
    var dogA = Dog()
    var dogB = dogA
    
    dogA.height = 50.0
    print("dogA.height -> \(dogA.height)")
    print("dogB.height -> \(dogB.height)")
    
    // dogA.height -> 50.0
    // dogB.height -> 50.0
    

    如果声明一个引用类型的常量,那么就意味着该常量的引用不能改变(即不能被同类型变量赋值),但指向的内存中所存储的变量是可以改变的。

    let dogC = Dog()
    dogC.height = 50
    
    // WRONG: dogC = dogA
    

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

    print(Unmanaged.passUnretained(dogA).toOpaque())
    print(Unmanaged.passUnretained(dogB).toOpaque())
    
    // 0x0000600000031380
    // 0x0000600000031380
    

    在 Swift 中,三等号(=== & !==)可以用来比较引用类型的引用(即指向的内存地址)是否一致。也可以在遵守 Equatable协议后,使用双等号(== & !=)用来比较变量的内容是否一致。

    参数 与 inout

    定义一个 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))"
        }
    }
    
    
    函数传参
    • 在 Swift 中,函数的参数默认为常量,即在函数体内只能访问参数,而不能修改参数值。具体来说:
      • 1.值类型作为参数传入时,函数体内部不能修改其值
      • 2.引用类型作为参数传入时,函数体内部不能修改其指向的内存地址,但是可以修改其内部的变量值
    • 但是如果要改变参数值或引用,那么就可以在函数体内部直接声明同名变量,并把原有变量赋值于新变量,那么这个新的变量就可以更改其值或引用。

    当值类型的变量作为参数被传入函数时,相当于创建了新的常量并初始化为传入的变量值,该参数的作用域及生命周期仅存在于函数体内。

    当引用类型的变量作为参数被传入函数时,相当于创建了新的常量并初始化为传入的变量引用,当函数体内操作参数指向的数据,函数体外也受到了影响。

    inout

    • inout 是 Swift 中的关键字,可以放置于参数类型前,冒号之后。使用 inout 之后,函数体内部可以直接更改参数值,而且改变会保留。
    func swap(resSct: inout ResolutionStruct) {
        withUnsafePointer(to: &resSct) { print("During calling: \($0)") }
        let temp = resSct.height
        resSct.height = resSct.width
        resSct.width = temp
    }
    
    var iPhone6ResoStruct = ResolutionStruct(height: 1334, width: 750)
    print(iPhone6ResoStruct)
    withUnsafePointer(to: &iPhone6ResoStruct) { print("Before calling: \($0)") }
    swap(resSct: &iPhone6ResoStruct)
    print(iPhone6ResoStruct)
    withUnsafePointer(to: &iPhone6ResoStruct) { print("After calling: \($0)") }
    
    // ResolutionStruct(height: 1334.0, width: 750.0)
    // Before calling: 0x000000011ce62f50
    // During calling: 0x000000011ce62f50
    // ResolutionStruct(height: 750.0, width: 1334.0)
    // After calling: 0x000000011ce62f50
    
    

    值类型变量作为参数传入函数,外界和函数参数的内存地址一致,函数内对参数的更改得到了保留。

    • 需要注意的是:

      • 使用 inout 关键字的函数,在调用时需要在该参数前加上 & 符号
      • inout 参数在传入时必须为变量,不能为常量或字面量(literal)
      • inout 参数不能有默认值,不能为可变参数
      • inout 参数不等同于函数返回值,是一种使参数的作用域超出函数体的方式
      • 多个 inout 参数不能同时传入同一个变量,因为拷入拷出的顺序不定,那么最终值也不能确定
    • inout 参数的传递过程:

      • 当函数被调用时,参数值被拷贝
      • 在函数体内,被拷贝的参数修改
      • 函数返回时,被拷贝的参数值被赋值给原有的变量

    官方称这个行为为:copy-in copy-out 或 call by value result。我们可以使用 KVO 或计算属性来跟踪这一过程,这里以计算属性为例。排除在调用函数之前与之后的 center GETTER call,从中可以发现:参数值先被获取到(setter 被调用),接着被设值(setter 被调用)。

    根据 inout 参数的传递过程,可以得知:inout 参数的本质与引用类型的传参并不是同一回事。inout 参数打破了其生命周期,是一个可变浅拷贝。

    嵌套类型

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


      值类型嵌套值类型
    值类型嵌套引用类型

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

    值类型嵌套引用类型
    引用类型嵌套值类型

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

    引用类型嵌套值类型
    引用类型嵌套引用类型

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

    引用类型嵌套引用类型

    相关文章

      网友评论

          本文标题:Swift 中的值类型与引用类型

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