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

值类型:类似于本地的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、初始化方法、构造器、构造方法)
  • 编译器会根据情况,可能会为结构体生成多个初始化器,
    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.size)
print(MemoryLayout.stride)
print((MemoryLayout.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的值。

接下来我们思考一个问题

通过上文我们知道:

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

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

你可能感兴趣的:(Swift进阶(四)--- 值类型 & 引用类型)