美文网首页
Swift进阶(四)--- 值类型 & 引用类型

Swift进阶(四)--- 值类型 & 引用类型

作者: Jax_YD | 来源:发表于2020-12-31 11:16 被阅读0次

    值类型:类似于本地的Excel,修改的内容只有自己知道。
    引用类型:类似于在线的表格,修改的内容大家都知道。
    在我们剖析值类型引用类型之前,我们向来回顾一下iOS的内存分区。

    内存分区.png
    • 栈区的地址 比 堆区的地址大
    • 是从高地址->低地址,向下延伸,由系统自动管理,是一片连续的内存空间
    • 是从低地址->高地址,向上延伸,由程序员管理,的空间结构类似于链表,不是连续的
    • 日常开发中的内存溢出是指堆栈溢出,可以理解为栈区堆区边界碰撞的情况
    • 全局区常量区都存储在Mach-o中的_ _TEXT cString段t

    接下里我们详细探讨一下值类型引用类型

    值类型

    我们来看下面的例子:

    func text() {
        var size = 10
        
        var size_2 = size
        
        size = 20
        
        print("第一次改变")
        print("size=\(size), size_2=\(size_2)")
        
        siz_2 = 30
        print("第二次改变")
        print("size=\(size), size_2=\(size_2)")
    }
    
    text()
    /************** 输出结果 **************/
    第一次改变
    size=20, size_2=10
    第二次改变
    size=20, size_2=30
    Program ended with exit code: 0
    
    • 从输出结果来看,sizesize_2符合 本地Excel 的特点,两个对象各自修改自己的值,对方都不知情。初步判定:sizesize_2值类型

    我们接着往下看,现在我们用LLDB来查看一下sizesize_2的内存结构

    image.png
    • 从上图中,我们可以看出来,sizesize_2的内存地址符合栈内存的情况,是连续的(且是从高到底的),相差了8个字节。而这8个字节正好是一个Int

    我们再来看一张图:


    image1.png
    • 这一次我们总共设置了两个断点,通过两次LLDB调试,我们不仅能从内存地址的大小来断定sizesize_2是两个连续的内存地址,而且,从变化也可以清晰的分辨出来。

    总结:值类型的特点:
    1、值类型存储在
    2、地址中存储的就是
    3、值类型的传递过程中会产生新的副本,是深拷贝
    4、值类型赋值给varlet或者给函数传参,是直接将所有内容拷贝一份

    注意:

    • Swift标准库中,为了提升性能,StringArrayDictionarySet采取了Copy On Write的技术。
      • 比如仅当有 “写” 操作时,才会真正执行拷贝操作
      • 对于标准库值类型的赋值操作,Swift能确保最佳性能,所以没必要为了保证最佳性能来避免赋值
      • 注意:Copy On Write 支队Swift标准库起作用
    • 建议:不需要修改的,尽量定义成 let

    结构体

    • 在Swift标准库中,绝大多数的公开类型都是结构体,而枚举和类只占很小一部分。
      • 比如:BoolIntDoubleStringArrayDictionary等常见的类型都是结构体
    struct Date {
        var year: Int
        var month: Int
        var day: Int
    }
    var date = Date(year: 2020, month: 12, day: 17)
    
    • 所有的结构体都有一个编译器自动生成的初始化器(initializer、初始化方法、构造器、构造方法)
    • 编译器会根据情况,可能会为结构体生成多个初始化器,\color{red}{宗旨是:保证所有成员都有初始值}
      image2.png
    • 如果将结构体内的属性定义成可选类型会怎样呢?
      • 通过下图可以看到,编译器并不会报错
      • 因为可选项都有一个默认值nil,因此可以编译通过
        image3.png
    • 当然,我们也可以自定义初始化器。
      • 一旦在定义结构体时自定义了初始化器,编译器就不会再帮它自动生成其他初始化器,如下图所示:


        image4.png
    • 接下来我们通过汇编来窥探一下初始化器的本质
      • 下面给出两个结构体,通过汇编来看一下init
    struct Date {
        var year: Int
        var month: Int
    
        init() {
            year = 2020
            month = 12
        }
    }
    var date = Date()
    
    struct Date {
        var year: Int = 2020
        var month: Int = 12
    }
    var date = Date()
    

    1、首先我们来看第一个结构体(进行断点调试):


    image5.png image6.png

    2、我们再来看一下第二个结构体(进行断点调试):


    image7.png image8.png image9.png
    • 通过对比上面连个结构体进入的init()方法,我们发现,最后进入的init()方法是一模一样的,没有任何区别。
    • 因此可以得出结论,结构体的初始化器无论是编译器默认的还是自定义的,其实没有本质区别。
    • 结构体的内存结构
    struct Date {
        var year = 2020
        var month = 12
        var Good = true
    }
    print(MemoryLayout<Date>.size)
    print(MemoryLayout<Date>.stride)
    print((MemoryLayout<Date>.alignment))
    /********** 输出结果 ************/
    17
    24
    8
    
    • 当然,此时我们也可以用withUnsafePointer函数去查看结构体的内存地址。
    • 通过内存结构的查看,我们发现,结构体也是值类型

    总结:
    1、结构体值类型,且结构体的地址是第一个成员的内存地址(可通过LLDB查看)
    2、结构体默认初始化器自定义初始化器没有任何区别
    3、结构体一旦在定义结构体时自定义了初始化器,编译器就不会再帮它自动生成其他初始化器
    4、所有的结构体都有一个编译器自动生成的初始化器
    5、编译器会根据情况,可能会为结构体生成多个初始化器,宗旨是:保证所有成员都有初始值

    引用类型

    eg:

    class Man {
        var age = 30
        var height: Int?
        
        
        init(_ age: Int) {
            self.age = age
        }
        
        init(height: Int) {
            self.height = height
        }
        
        init(_ age: Int, _ height: Int) {
            self.age = age
            self.height = height
        }
    }
    
    let M1 = Man.init(20)
    let M2 = Man.init(height: 180)
    let M3 = Man.init(18, 190)
    
    • 在类中,如果属性没有赋值,且为非可选项,此时需要自定义init方法

    为什么类是引用类型?

    eg:

    class Man {
        var age = 30
        var height: Int?
    }
    let M1 = Man.init()
    

    下面我们断点调试一下M1

    image.png
    • 通过上图可以看到,M1里面存放的是地址。
      接下来我们通过值的修改来观察一下引用类型的变化(注意:上文中M1是let类型,接下来我们需要用var类型)
      image.png
    • 通过上图我们可以看到,M1M2里面存放的地址是一样的,因此var M2 = M1是地址拷贝,即浅拷贝。并且我们可以观察到M2.age的改变也影响到了M1.age的值。

    接下来我们思考一个问题
    \color{red}{如果结构体内存放类对象,会是什么情况?}
    通过上文我们知道:

    • 结构体值类型
    • 引用类型
      那么结构体的结合会影响到对方的存储形式吗?
      image.png
    • 通过上图,我们可以看到p中的M1仍然存放的是地址,由此可见结构体中包含类对象并不会改变其存储形式。
    • 反过来也是一样的,包含结构体也不会形象结构体的存储形式,有兴趣的同学可以自己尝试一样,过程跟上面的一样。

    总结:
    1、引用类型
    2、类对象里面存储的是地址,类对象之间的赋值属于地址传递(浅拷贝)
    3、值类型引用类型互相包含的情况下,并不会改变各自的存储形式。

    相关文章

      网友评论

          本文标题:Swift进阶(四)--- 值类型 & 引用类型

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